在我写的一个小游戏中,我有一个带有两个构造函数的类Weapon
,一个用于获取一些参数来生成一个自定义武器,另一个用于获取一个默认武器({{} 1}}):
CHAIN_GUN
问题:使用Weapon::Weapon (void) {
// Standard weapon
*this = getWeapon(CHAIN_GUN);
return;
}
和*this
初始化课程会产生任何负面影响吗?
答案 0 :(得分:33)
想象一下,有人要求你画一幅画......你会不会;
- 首先绘制你的默认( 1st )(你非常喜欢那个熟悉的笑脸),
- 然后绘制有人要求的内容( 2nd ),
- 只是再次绘制相同的东西,但在包含默认的画布上,
- 然后刻录第二次画?
这篇文章将试图解释为什么这个明喻是相关的。
为什么这是一个不好的想法?
我从未见过使用赋值运算符实现的默认构造函数,老实说,这不是我推荐的,也不支持代码审核。
这样的代码的主要问题是,根据定义,我们构建两个对象(而不是一个)并调用成员函数,这意味着我们构造所有成员两次,后来必须通过调用赋值运算符来复制/移动初始化所有成员。
在请求构建 1 对象时,我们构造 2 对象,但稍后将值从 2nd 复制到 1st 并弃掉 2nd 。
结论:不要这样做。
(注意:如果Weapon
有基类,则会更糟糕)
(注意:另一个潜在的危险是工厂函数意外地使用默认构造函数,导致在编译期间没有捕获到无限递归,如@ Ratchet Freat所示)
建议的解决方案
在您的特定情况下,您最好在构造函数中使用 default-argument ,如下例所示。
class Weapon {
public:
Weapon(WeaponType w_type = CHAIN_GUN);
...
}
Weapon w1; // w_type = CHAIN_GUN
Weapon w2 (KNOWLEDGE); // the most powerful weapon
(注意:上述选择的另一种方法是使用{+ 3}},可在C ++ 11中使用)
答案 1 :(得分:16)
使用赋值运算符实现构造函数很少是个好主意。例如,在您的情况下,您可以使用默认参数:
Weapon::Weapon(GunType g = CHAIN_GUN)
: // Initialize members based on g
{
}
在其他情况下,您可以使用委托构造函数(使用C ++ 11或更高版本):
Weapon::Weapon(GunType g)
: // Initialize members based on g
{
}
Weapon::Weapon()
: Weapon(CHAIN_GUN) // Delegate to other constructor
{
}
答案 2 :(得分:7)
要记住的一件事是,如果operator=
- 或它调用的任何函数 - 是virtual
,则派生类版本 赢了 被调用。这可能会导致未初始化的字段和以后的未定义行为,但这一切都取决于您的数据成员。
更一般地说,如果你的base和数据成员有构造函数或者出现在初始化列表中(或者在类声明中赋值给C ++ 11),那么你的base和data成员都会被保证初始化 - 所以除了{{1在上面的问题中,virtual
通常可以在没有未定义的行为的情况下工作。
如果在调用operator=
之前已初始化基础或成员,则在使用之前初始值将被覆盖,优化器可能会或可能不会删除第一次初始化。例如:
operator=()
正如您所看到的,它有点容易出错,即使您说得对,后来更新std::string s_;
Q* p_;
int i_;
X(const X& rhs)
: p_(nullptr) // have to initialise as operator= will delete
{
// s_ is default initialised then assigned - optimiser may help
// i_ not initialised but if operator= sets without reading, all's good
*this = rhs;
}
的人可能也不会检查使用它的构造函数(ab)....
如果operator=
使用Prototype或Flyweight模式并尝试复制构造它返回的getWeapon()
,则最终会导致无限递归导致堆栈溢出。
退后一步,问题是Weapon
存在于那种形式中的原因。如果我们需要一个基于武器类型创建武器的函数,那么getWeapon(CHAIN_GUN);
构造函数似乎是一个合理的选择。也就是说,有一些罕见但丰富的边缘情况,Weapon(Weapon_Type);
可能会返回除getWeapon
对象以外的其他内容,而这些对象永远不会被分配给Weapon
,或者可能会保持独立出于构建/部署原因....
答案 3 :(得分:3)
如果你定义了一个非复制=
赋值运算符,它允许Weapon
在构造之后改变它的类型,那么在赋值方面实现构造函数就可以了,并且是一个很好的方法集中初始化代码。但是如果Weapon
并不意味着在构造之后改变类型,那么非复制=
赋值运算符没有多大意义,更不用说用于初始化。
答案 4 :(得分:3)
我确定是的。
你已经在" getWeapon"中创建了对象。功能,然后你复制它,这可能是长时间的操作。所以,至少你必须尝试移动语义。
但是。 如果在里面" getWeapon"你打电话给构造函数(并且你做了,不知何故" getWeapon"必须创建你的类以将它返回到你的复制操作),你创建非常不清楚的架构,当一个构造函数调用调用另一个构造函数的函数时。
我相信您必须将参数初始化分离为私有函数,必须以您希望的方式从构造函数中调用它们。