【问题标题】:Why is a C++ class's destructor called after assigning using a converting constructor?为什么在使用转换构造函数赋值后调用 C++ 类的析构函数?
【发布时间】:2020-10-29 11:50:28
【问题描述】:
鉴于这个类:
class Baz
{
public:
Baz() : Baz(0) {}
Baz(int i) { _i = i; }
Baz(Baz const &b) { _i = b._i * 10; }
~Baz() { }
private:
int _i;
};
如果我调试并单步执行此代码:
Baz a = 4;
正如预期的那样,调用了采用int 的Baz 构造函数,我得到了一个Baz,_i 为4。这很好。
但如果我这样做:
Baz b;
b = 4;
正如预期的那样,第一行调用了默认构造函数。第二行调用int 构造函数然后调用析构函数。
我的第一个期望是第二个示例的第二行将简单地调用int 构造函数来分配给b。我没想到会调用析构函数,但如果它在赋值之前首先将整数4 转换为Baz,那么之后销毁它是有意义的。但是我希望在将该临时值分配给b 时调用复制构造函数。被销毁的Baz 具有4 的值_i,因此它不是在第一行创建的对象。
这里到底发生了什么?为什么这两种情况有区别?
【问题讨论】:
标签:
c++
class
constructor
destructor
copy-constructor
【解决方案1】:
这一行:
b = 4;
首先调用Baz的int构造函数,构造一个临时的Baz,值为4。这个临时在分号处被销毁。
赋值不调用拷贝构造函数,而是实际调用拷贝赋值运算符,其签名为Baz& operator=(Baz const &);。
【解决方案2】:
这一行:
Baz a = 4;
相当于:
Baz a(4); // It simply constructs the object.
// Note the `=` is not an assignment when
// used in a declaration like this. It is
// simply short hand (syntactic sugar) for
// a single argument constructor.
虽然这两行:
Baz b; // Default construct thus setting _i to zero.
b = 4; // This **IS** an assignment and needs an assignment
// operator (or there is a compiler error).
// Note there is no assignment operator that takes
// an integer. But there is a default assignment
// operator that takes a Baz by const reference.
//
// This is auto generated by the compiler.
// See rule of 5
// Baz& operator=(Baz const& copy): _i(copy._i){return *this)
//
// So the compiler must convert the integer to Baz before
// an assignment is allowed. The compiler is allowed to
// create an Object using a one parameter constructor so
// that line is equivalent too:
// Equiv
b = Baz(4); // So this creates a temporary Baz object here.
// Then the assignment operator is called.
// Then at the end of the statement the temporary
// object must be destroyed so we call the destructor
// on the temporary.
【解决方案3】:
语句Baz a = 4;只是Baz a(4);的语法糖,所以它直接调用Baz(int)转换构造函数。
Baz 没有定义operator=(int),但它有一个operator=(const Baz&)(在您的示例中由编译器生成)。而Baz 有一个Baz(int) 构造函数。 const Baz& 参数可以绑定到右值,并且从 int 到 Baz 的隐式转换只有 1 次。因此,编译器能够将语句b = 4; 视为b = Baz(4);,即它创建一个临时的Baz 对象并将其传递给operator=。临时在创建它的语句结束时被销毁(即在;)。
所以,您看到的是临时 Baz 对象的 int 构造函数和析构函数。
【解决方案4】:
构造函数只能用于创建新对象。
b = 4; 不会创建b,因此它不能在b 上调用构造函数。相反,它使用Baz(int i) 构造函数创建一个临时的Baz 实例,然后将临时分配给b(使用operator=,在您的情况下由编译器生成)。
临时在完整表达式的末尾被销毁(即当控制到达;时),这就是你看到析构函数调用的原因。