使用*this初始化一个类有什么后果吗?

KKO*_*nge 30 c++ initialization this

在我写的一个小游戏中,我有一个Weapon带有两个构造函数的类,一个用于获取一些参数来生成一个自定义武器,另一个用于获取一个默认武器CHAIN_GUN:

Weapon::Weapon (void) {
    // Standard weapon
    *this = getWeapon(CHAIN_GUN);
    return;
}
Run Code Online (Sandbox Code Playgroud)

问题:使用*thisoperator=初始化课程是否会产生任何负面影响?

Fil*_*efp 33

想象一下,有人要求你画一幅画......你会吗?

  • 先画出你的默认(第一个)(你熟悉的那个熟悉的笑脸),
  • 然后画出有人要求的东西(第二),
  • 只是再画一次同样的东西,但是在包含默认值的画布上,
  • 然后燃烧第二幅画?

这篇文章将试图解释为什么这个明喻是相关的.


为什么这是一个不好的想法?

我从未见过使用赋值运算符实现的默认构造函数,老实说,这不是我推荐的,也不是代码审查期间的支持.

这样的代码的主要问题是,根据定义,我们构造两个对象(而不是一个)并调用一个成员函数,这意味着我们构造所有成员两次,然后必须复制/移动初始化所​​有成员调用赋值运算符.

在请求构造1个对象时,我们构造了2个对象,只是稍后将值从第2个复制到第1个并丢弃第2 ,这是不直观的.

结论:不要这样做.

(注意:在Weapon有基类的情况下会更糟)

(注意:另一个潜在的危险是工厂函数意外地使用default-constructor,导致在编译期间没有捕获到无限递归,如@Tychet Freat所述)


建议的解决方案

在您的特定情况下,您最好在构造函数中使用default-argument,如下例所示.

class Weapon {
public:
  Weapon(WeaponType w_type = CHAIN_GUN);
  ...
}
Run Code Online (Sandbox Code Playgroud)

Weapon w1;             // w_type = CHAIN_GUN
Weapon w2 (KNOWLEDGE); // the most powerful weapon
Run Code Online (Sandbox Code Playgroud)

(注意:上面的替代方法是使用C++ 11中提供的委托构造函数)

  • 并且存在工厂使用默认构造函数(现在或稍后在无知的更改中)导致无限递归的危险 (2认同)
  • 将`explicit`放在单参数构造函数前面通常很好,以避免隐式类型转换. (2认同)

Vau*_*ato 16

使用赋值运算符来实现构造函数很少是个好主意.例如,在您的情况下,您可以使用默认参数:

Weapon::Weapon(GunType g = CHAIN_GUN)
: // Initialize members based on g
{
}
Run Code Online (Sandbox Code Playgroud)

在其他情况下,您可以使用委托构造函数(使用C++ 11或更高版本):

Weapon::Weapon(GunType g)
: // Initialize members based on g
{
}

Weapon::Weapon()
: Weapon(CHAIN_GUN) // Delegate to other constructor
{
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,委托构造函数需要 C++11 或更高版本。 (2认同)

Ton*_*roy 7

要记住的一件事是,如果operator=- 或者它调用的任何函数 - 是virtual,则不会调用派生类版本.这可能会导致未初始化的字段和以后的未定义行为,但这一切都取决于您的数据成员.

更一般地说,如果你的基础和数据成员有构造函数或出现在初始化列表中(或者在类声明中指定了C++ 11),那么它们的基础和数据成员都会被保证初始化 - 所以除了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;
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,它有点容易出错,即使你说得对,后来更新的人operator=可能也不会检查使用它的构造函数(ab)....

如果getWeapon()使用Prototype或Flyweight模式并尝试复制构造Weapon它返回,最终会导致无限递归导致堆栈溢出.


退后一步,问题是为什么getWeapon(CHAIN_GUN);存在这种形式.如果我们需要一个基于武器类型创建武器的函数,那么Weapon(Weapon_Type);构造函数似乎是一个合理的选择.也就是说,有一些罕见但丰富的边缘情况getWeapon可能会返回除了一个Weapon永远不会被分配给a 的对象以外的东西Weapon,或者可能因为构建/部署原因而保持独立....