c++ - C ++ - 将带有引用的长参数列表重构为结构
问题描述
我喜欢在调用它们的构造函数后简单地拥有具有有效状态的类 - 即所有必需的依赖项都传递到构造函数中。
我也喜欢将所需的依赖项作为引用传递,因为在编译时简单地禁止 nullptr 作为这些参数的值。
例子:
class B;
class A
{
public:
A(B& b) : b(b) {}
private:
B& b;
}
在实例化 A 之后,您(几乎)可以保证该实例处于有效状态。我发现这种代码风格可以避免编程错误。
我的问题与当它们有很多依赖关系时重构这些类有关。
例子:
// Includes for B, C, D, E, F...
class A
{
public:
A(B b, C c, D d, E e, F f) : b(b), c(c), d(d), e(e), f(f) {}
private:
B b;
C c;
D d;
E e;
F f;
}
通常,我将一长串参数放在结构中,如下所示:
struct Deps
{
B b;
C c;
D d;
E e;
F f;
}
class A
{
public:
A(Deps deps) : b(deps.b), c(deps.c), d(deps.d), e(deps.e), f(deps.f) {}
private:
B b;
C c;
D d;
E e;
F f;
}
这样,它使调用站点更加明确并且更不容易出错:由于必须命名所有参数,因此您不会因为将它们以错误的顺序错误地切换它们而面临风险。
可悲的是,该技术在参考文献中效果不佳。在 Deps 结构中有引用会将问题转发给该结构:然后,Deps 结构需要有一个初始化引用的构造函数,然后该构造函数将有一个很长的参数列表,基本上什么也解决不了。
现在的问题是:有没有办法在包含引用的构造函数中重构长参数列表,这样没有函数会导致参数列表很长,所有参数始终有效,并且类的任何实例都不会处于无效状态(即某些依赖项未初始化或为空)?
解决方案
实际上,有一个非常优雅/简单的解决方案,使用std::tuple
:
#include <tuple>
struct A{};
struct B{};
struct C{};
struct D{};
struct E{};
struct F{};
class Bar
{
public:
template<class TTuple>
Bar(TTuple refs)
: a(std::get<A&>(refs))
, b(std::get<B&>(refs))
, c(std::get<C&>(refs))
, d(std::get<D&>(refs))
, e(std::get<E&>(refs))
, f(std::get<F&>(refs))
{
}
private:
A& a;
B& b;
C& c;
D& d;
E& e;
F& f;
};
void test()
{
A a; B b; C c; D d; E e; F f;
// Different ways to incrementally build the reference holder:
auto tac = std::tie(a, c); // This is a std::tuple<A&, C&>.
auto tabc = std::tuple_cat(tac, std::tie(b));
auto tabcdef = std::tuple_cat(tabc, std::tie(d, f), std::tie(e));
// We have everything, let's build the object:
Bar bar(tabcdef);
}
std::tie
精确地存在以创建引用元组。我们可以使用std::tuple_cat
. 并std::get<T>
允许从给定的元组中准确检索我们需要的引用。
这有:
最小样板:您只需
std::get<X&>
在每个引用类型的成员初始化器列表中写入。无需提供/重复任何其他内容即可将其用于更多引用或包含引用的类型。完全编译时安全:如果您忘记提供引用或提供两次,编译器会报错。类型系统对所有必要的信息进行编码。
添加引用的顺序没有限制。
没有手写模板机械。使用标准工具而不是手写模板机制意味着您不会引入错误/忘记极端情况。这也意味着这种方法的用户/读者没有任何需要涉水的东西(并且可能会逃跑,尖叫)。
我认为这是一个非常简单的解决方案,只是因为std::tuple
和朋友们已经实现了这里需要的所有元编程。它仍然比“一个长列表中的所有内容”稍微复杂一些,但我很确定这将是值得的权衡。
(我以前的手写模板版本存在于编辑历史中。但我意识到这std::tuple
已经完成了我们需要的一切。)
推荐阅读
- javascript - 函数已定义但从未使用
- ios - 可以从类中获取视图大小吗?(迅速)
- python - 字典键成矩阵
- mongodb - 如何在堆栈 MERN 中建立这种关系?
- verilog - 具有启用和异步复位功能的 4 位寄存器
- ruby-on-rails - 如何在Ruby中将tiff(字符串)的base64数据转换为png(字符串)的base64数据?
- java - applicationRunner 在 Spring Boot 中是如何运行的?
- python - 如何从 DataFrame/Matplot 获取数据
- javascript - 如果Javascript中的值相同,如何比较和删除数组中的对象?
- reactjs - 如何使无效的挂钩呼叫消失?