我最近遇到了使用配置对象而不是通常的setter方法进行配置的类。一个小例子:
class A {
int a, b;
public:
A(const AConfiguration& conf) { a = conf.a; b = conf.b; }
};
struct AConfiguration { int a, b; };
好处:
A(const AConfiguration& conf = AConfiguration())
。缺点:
我缺少更多的缺点吗?如果没有:为什么不经常使用?
答案 0 :(得分:7)
无论是单独传递数据还是按结构传递数据都是一种风格问题,需要根据具体情况来决定。
重要的问题是:对象在构造之后是否已准备好并且可用,编译器是否强制将所有必要的数据传递给构造函数,或者您是否必须记住在构造之后调用一堆setter可能会增加在任何时候,没有编译器给你任何提示,你需要调整你的代码。所以这是否是
A(const AConfiguration& conf) : a(conf.a), b(conf.b) {}
或
A(int a_, int b_) : a(a_), b(b_) {}
并不重要。 (有很多参数,每个人都更喜欢前者,但是这个数字 - 以及这样的类是否设计得很好 - 是值得商榷的。)但是,我是否可以使用这样的对象
A a1(Configuration(42,42));
A a2 = Configuration(4711,4711);
A a3(7,7);
或必须这样做
A urgh;
urgh.setA(13);
urgh.setB(13);
在我可以使用该对象之前,确实会产生巨大的差异。特别是当有人出现并向A
添加另一个数据字段时。
答案 1 :(得分:4)
使用此方法可以更轻松地实现二进制兼容性。
当库版本更改并且配置struct
包含它时,构造函数可以区分是否传递“旧”或“新”配置,并在访问不存在时避免“访问冲突”/“段错误”字段。
此外,保留了构造函数的受损名称,如果更改了其签名,则会更改。这也让我们保持二进制兼容性。
示例:
//version 1
struct AConfiguration { int version; int a; AConfiguration(): version(1) {} };
//version 2
struct AConfiguration { int version; int a, b; AConfiguration(): version(2) {} };
class A {
A(const AConfiguration& conf) {
switch (conf.version){
case 1: a = conf.a; b = 0; // No access violation for old callers!
break;
case 2: a = conf.a; b = conf.b; // New callers do have b member
break;
}
}
};
答案 2 :(得分:2)
主要的好处是A对象可以是不可变的。我不知道AConfiguration stuct是否能够为构造函数提供a和b参数的任何好处。
答案 3 :(得分:1)
使用此方法会使二进制兼容性变得更难。
如果更改了结构(添加了一个新的可选字段),则使用该类的所有代码可能都需要重新编译。如果添加了一个新的非虚拟setter函数,则不需要重新编译。
答案 4 :(得分:0)
我支持降低二进制兼容性。
我看到的问题来自对struct结构字段的直接访问。
struct AConfig1 { int a; int b; };
struct AConfig2 { int a; std::map<int,int> b; }
由于我修改了b
的表示,我被搞砸了,而是:
class AConfig1 { public: int getA() const; int getB() const; /* */ };
class AConfig2 { public: int getA() const; int getB(int key = 0) const; /* */ };
对象的物理布局可能有所改变,但我的getter没有,并且函数的偏移也没有。
当然,对于二进制兼容性,应该查看PIMPL成语。
namespace details { class AConfigurationImpl; }
class AConfiguration {
public:
int getA() const;
int getB() const;
private:
AConfigurationImpl* m_impl;
};
虽然您最终编写了更多代码,但只要在现有代码之后添加补充方法,就可以保证对象的向后兼容性。
实例在内存中的表示不依赖于方法的数量,它只取决于:
这是可见的(不是可访问的)。
在此我们保证不会对属性进行任何更改。 AConfigurationImpl
的定义可能没有任何问题地改变,方法的实现也可能会改变。
代码越多意味着:构造函数,复制构造函数,赋值运算符和析构函数,这是一个相当大的数量,当然还有getter和setter。另请注意,这些方法不能再内联,因为它们的实现是在源文件中定义的。
无论是否适合你,你都可以自己决定。