web-dev-qa-db-ja.com

デフォルトの仮想デストラクタは、コンパイラによって生成された移動操作を防止しますか?

投稿に触発されてデストラクタが暗黙の移動メソッドの生成を無効にするのはなぜですか?、同じことが当てはまるかどうか疑問に思いましたデフォルトの仮想デストラクタ、例:.

class WidgetBase // Base class of all widgets
{
    public:
        virtual ~WidgetBase() = default;
        // ...
};

クラスはウィジェット階層の基本クラスであることが意図されているため、基本クラスポインターを操作するときのメモリリークや未定義の動作を回避するために、デストラクタを仮想的に定義する必要があります。一方で、コンパイラーが自動的に移動操作を生成するのを防ぎたくありません。

デフォルトの仮想デストラクタは、コンパイラによって生成された移動操作を防止しますか?

31
tommyk

はい、デストラクタを宣言すると、moveコンストラクタの暗黙的な宣言が防止されます。

N3337 [class.copy]/9:クラスXの定義で移動コンストラクターが明示的に宣言されていない場合、その場合に限り、デフォルトとして暗黙的に宣言されます。

  • Xには、ユーザーが宣言したコピーコンストラクターがありません。
  • Xには、ユーザーが宣言したコピー代入演算子がありません。
  • Xには、ユーザーが宣言したムーブ代入演算子がありません。
  • Xにはユーザー宣言のデストラクタがありません、および
  • 移動コンストラクターは、削除済みとして暗黙的に定義されません。

デストラクタを宣言し、それをdefaultとして定義すると、user-declaredとしてカウントされます。

移動コンストラクターを宣言し、自分でdefaultとして定義する必要があります。

WidgetBase(WidgetBase&&) = default;

これにより、コピーコンストラクターがdeleteとして定義されるため、これもdefaultする必要があることに注意してください。

WidgetBase(const WidgetBase&) = default;

コピー代入演算子とムーブ代入演算子のルールも非常に似ているため、必要に応じてdefaultする必要があります。

26
TartanLlama

解決策ではありませんが、考えられる回避策の1つです。デフォルトの仮想デストラクタしかないクラスからすべてのクラスを継承できます。

GCC9とAppleのClang ++を-std=c++17で使用して確認しました。どちらも、以下のクラスを継承するクラスの移動コンストラクターを生成します。

class Object {
public:
    virtual ~Object() = default;
};

以下のクラスには、実際にmoveコンストラクターがあります。

class Child : public Object {
public:
    Child(std::string data) : data(data) {
    }

private:
    std::string data;

};

もう1つの考えられるが危険な回避策は、仮想デストラクタをまったく宣言しないことです。次のリスクが発生します。

  • すべてのオブジェクトは、正確なタイプを知っている誰かによって常に破壊されなければなりません。これは、うまく設計されたC++コードではそれほど大きな問題ではありません。
  • このようなクラスのオブジェクトがstd::vectorstd::listのようなコンテナに格納されている場合は、常にstd::shared_ptrを使用してラップする必要があります。 std::unique_ptrはリークを引き起こす可能性があります!これは、削除機能の保存に関連する違いに関連しています。
0
nikitakatchik