从赋值运算符调用析构函数有什么意想不到的后果吗?
Are there any unexpected consequences of calling a destructor from the assignment operator?
例如:
class Foo : public Bar
{
~Foo()
{
// Do some complicated stuff
}
Foo &operator=(const Foo &rhs)
{
if (&rhs != this)
{
~Foo(); // Is this safe?
// Do more stuff
}
}
}
对于继承和其他类似的事情,显式调用析构函数是否会产生意想不到的后果?
是否有任何理由将析构函数代码抽象为 void destruct()
函数并调用它?
在最简单的情况下调用析构函数是一个糟糕的主意,而在代码变得稍微复杂一点的时候调用析构函数是一个可怕的主意。
最简单的情况是这样的:
class Something {
public:
Something(const Something& o);
~Something();
Something& operator =(const Something& o) {
if (this != &o) {
// this looks like a simple way of implementing assignment
this->~Something();
new (this) Something(o); // invoke copy constructor
}
return *this;
}
};
这是个坏主意。如果复制构造函数抛出异常,您将剩下原始内存——那里没有对象。只是,在赋值运算符之外,没有人注意到。
如果继承发挥作用,事情会变得更糟。假设 Something
实际上是一个带有虚拟析构函数的基 class。派生的class的功能都是默认实现的。在这种情况下,派生的 class 的赋值运算符将执行以下操作:
- 它将调用自己的基础版本(您的赋值运算符)。
- 您的赋值运算符调用了析构函数。这是虚拟通话。
- 派生析构函数销毁派生class.
的成员
- 基类析构函数销毁基类的成员class。
- 您的赋值运算符调用复制构造函数。这不是虚拟的;它实际上构造了一个基础对象。 (如果基础 class 不是抽象的。如果是,代码将无法编译。)您现在已经用基础对象替换了派生对象。
- 复制构造函数构造基的成员。
- 派生赋值运算符对派生 class 的成员进行成员赋值。哪些已被销毁而不是重新创建。
此时,你有多个 UB 实例相互堆积,完全混乱。
是的。不要那样做。
绝对不会。
void f()
{
Foo foo, fee;
foo = fee; <-- a first foo.~Foo will be called here !
} <-- a second foo.~Foo will be called here and fee.~Foo as well !
如您所见,您调用了 3 次析构函数,而不是预期的 2 次调用。
您应该不在构造函数或非静态方法中使用*self-*析构函数。
例如:
class Foo : public Bar
{
~Foo()
{
// Do some complicated stuff
}
Foo &operator=(const Foo &rhs)
{
if (&rhs != this)
{
~Foo(); // Is this safe?
// Do more stuff
}
}
}
对于继承和其他类似的事情,显式调用析构函数是否会产生意想不到的后果?
是否有任何理由将析构函数代码抽象为 void destruct()
函数并调用它?
在最简单的情况下调用析构函数是一个糟糕的主意,而在代码变得稍微复杂一点的时候调用析构函数是一个可怕的主意。
最简单的情况是这样的:
class Something {
public:
Something(const Something& o);
~Something();
Something& operator =(const Something& o) {
if (this != &o) {
// this looks like a simple way of implementing assignment
this->~Something();
new (this) Something(o); // invoke copy constructor
}
return *this;
}
};
这是个坏主意。如果复制构造函数抛出异常,您将剩下原始内存——那里没有对象。只是,在赋值运算符之外,没有人注意到。
如果继承发挥作用,事情会变得更糟。假设 Something
实际上是一个带有虚拟析构函数的基 class。派生的class的功能都是默认实现的。在这种情况下,派生的 class 的赋值运算符将执行以下操作:
- 它将调用自己的基础版本(您的赋值运算符)。
- 您的赋值运算符调用了析构函数。这是虚拟通话。
- 派生析构函数销毁派生class. 的成员
- 基类析构函数销毁基类的成员class。
- 您的赋值运算符调用复制构造函数。这不是虚拟的;它实际上构造了一个基础对象。 (如果基础 class 不是抽象的。如果是,代码将无法编译。)您现在已经用基础对象替换了派生对象。
- 复制构造函数构造基的成员。
- 派生赋值运算符对派生 class 的成员进行成员赋值。哪些已被销毁而不是重新创建。
此时,你有多个 UB 实例相互堆积,完全混乱。
是的。不要那样做。
绝对不会。
void f()
{
Foo foo, fee;
foo = fee; <-- a first foo.~Foo will be called here !
} <-- a second foo.~Foo will be called here and fee.~Foo as well !
如您所见,您调用了 3 次析构函数,而不是预期的 2 次调用。
您应该不在构造函数或非静态方法中使用*self-*析构函数。