C++11 虚拟析构函数和移动特殊函数的自动生成

2021-12-17 00:00:00 inheritance c++ c++11 move-semantics

C++11 中自动生成特殊移动函数(构造函数和赋值运算符)的规则规定不能声明析构函数.逻辑大概是,如果你需要在破坏中做一些特殊的事情,那一步可能不安全.

The rules for auto generating special move functions (constructor and assignment operator) in C++11 specify that no destructor can be declared. The logic is presumably that, if you need to do something special in destruction, that a move may not be safe.

然而,为了在多态中正确调用析构函数,有必要将基类的析构函数声明为虚拟的(否则通过其基类的指针删除子类的实例将无法正确链接析构函数).

However, for proper destructor calls in polymorphism, it is necessary to declare a base classes' destructor as virtual (otherwise deleting an instance of a sub class through a pointer of its base class will not properly chain the destructor).

我假设,即使是空的析构函数也会阻止编译器自动生成特殊的移动函数.如:

I'm assuming, then, that even an empty destructor would prevent the compiler from automatically generating a special move functions. As in:

class Base {
    virtual ~Base() { }
};

但是,您可以默认析构函数,如下所示:

You can, however, default the destructor, as in:

class Base {
    virtual ~Base() = default;
}

那么问题 1:这会允许编译器自动生成特殊移动函数吗?

So question 1: Will this allow the compiler to auto generate special move functions?

然而,显式默认析构函数存在问题.至少在 GCC 4.8.2 的情况下,签名被隐式更改为 noexcept.如:

There is a problem with the explicit default destructor, however. In at least the case of GCC 4.8.2, the signature is implicitly changed to noexcept. As in:

class Base {
    virtual ~Base() = default; // compiler changes to:
    // virtual ~Base() noexcept;
}

虽然我在析构函数中使用 noexcept 没有问题,但这会破坏以下客户端"代码:

While I have no problem with noexcept in a destructor, this would break the following "client" code:

class Sub : public Base {
    virtual ~Sub(); // this declaration is now "looser" because of no noexcept
}

所以问题 2 更重要:有没有办法允许在 C++11 中自动生成特殊的移动函数并允许适当的析构函数链接到子类(如上所述),所有这些都不会破坏子类(客户端") 代码?

So question 2 is more to the point: is there a way to allow auto generation of special move functions in C++11 and allow proper destructor chaining to sub classes (as described above), all without breaking subclass ("client") code?

推荐答案

  1. 不,默认的析构函数仍被视为用户定义的,因此它将阻止移动操作的生??成.还要声明移动操作 default-ed 以使编译器生成它们.

  1. No, a defaulted destructor is still considered user defined, so it will prevent the generation of move operations. Also declare the move operations default-ed to make the compiler generate them.

您只需在基类中将移动操作声明为 default-ed.在派生类中,析构函数不再是用户定义的(除非您明确说明),因此不会删除移动操作.

You need to only declare the move operations as default-ed in the base class. In the derived class, the destructor won't be user defined anymore (unless you explicitly say so), so the move operations won't be deleted.

所以我要做的是:

class Base
{
    virtual ~Base() = default;
    Base(Base&&) = default;
    Base& operator=(Base&&) = default;
    // probably need to think about copy operations also, as the move disables them
    Base(const Base&) = default;
    Base& operator=(const Base&) = default;
};

我强烈推荐可能对移动语义贡献最大的人的这个演讲:http://www.slideshare.net/ripplelabs/howard-hinnant-accu2014

I highly recommend this talk by the person who contributed probably the most to the move semantics: http://www.slideshare.net/ripplelabs/howard-hinnant-accu2014

或者,如果您能亲自动手,您应该阅读 Scott Meyers 的优秀著作Effective Modern 中的条款 17:了解特殊成员函数生成C++.这个问题得到了很好的解释.

Or, if you can get your hands on, you should read the Item 17: Understand special member function generation from Scott Meyers' excellent book Effective Modern C++. This issue is excellently explained.

PS:我认为您应该多考虑一下您的基类.大多数情况下,您应该使用抽象类,因此无需复制/移动它们的实例.

PS: I think you should think a bit more about your base classes. Most of the time, you should use abstract classes, so there will be no need to copy/move instances of them.

PSS:我认为默认情况下,析构函数在 C++11/14 中被标记为 noexcept,所以不明确指定它不会导致任何问题:

PSS: I think by default destructors are marked noexcept in C++11/14, so not explicitly specifying it shouldn't cause any problems:

继承构造函数和隐式声明的默认值构造函数、复制构造函数、移动构造函数、析构函数、复制赋值运算符,移动赋值运算符都是noexcept(true) 默认情况下,除非他们需要调用一个函数即 noexcept(false),在这种情况下,这些函数是noexcept(false).

Inheriting constructors and the implicitly-declared default constructors, copy constructors, move constructors, destructors, copy-assignment operators, move-assignment operators are all noexcept(true) by default, unless they are required to call a function that is noexcept(false), in which case these functions are noexcept(false).

相关文章