web-dev-qa-db-ja.com

C ++ 11の仮想メンバーを持つ仮想デストラクタ

これらのスライドでは C++ 11/14標準について、スライド15で、著者はC++ 11では「多くの古典的なコーディング規則はもはや適用できない」と書いています。彼は3つの例のリストを提案し、私は3つのルールとメモリ管理に同意します。

ただし、彼の2番目の例は、「仮想メンバーを持つ仮想デストラクタ」です(まさにそれ)。 それはどういう意味ですか?次のようなものがある場合に適切なデストラクタを呼び出すには、基本クラスのデストラクタを仮想として宣言する必要があることを私は知っています

Base *b = new Derived;
...
delete b;

これはここでよく説明されています: 仮想デストラクタをいつ使用するか?

しかし、仮想メンバーがいる場合、仮想をデストラクタとして宣言することは、C++ 11では役に立たないのでしょうか?

30
Florian Richoux

スライドの作者として、私は明確にしようとします。

Derivedインスタンスをnewで明示的に割り当て、基本クラスポインタを使用してdeleteで破棄するコードを作成する場合は、virtualデストラクタを定義する必要があります。そうしないと、Derivedインスタンスが不完全に破棄されてしまいます。ただし、newdeleteを完全に控え、ヒープに割り当てられたポリモーフィックオブジェクトを参照するために_shared_ptr_のみを使用することをお勧めします。

_shared_ptr<Base> pb=make_shared<Derived>();
_

このように、共有ポインタは、_shared_ptr<Base>_がそれを表すために使用されている場合でも、使用される元のデストラクタを追跡します。最後に参照した_shared_ptr_がスコープ外になるかリセットされると、~Derived()が呼び出され、メモリが解放されます。したがって、~Base()を仮想化する必要はありません。

_unique_ptr<Base>_および_make_unique<Derived>_は、deleterに関して_shared_ptr_のメカニズムを提供しないため、この機能を提供しません。一意のポインターははるかに単純で、オーバーヘッドを最小限に抑えることを目的としているため、削除ツールに必要な追加の関数ポインターを格納していません。 _unique_ptr_の場合、削除関数は型の一部であるため、_~Derived_を参照する削除機能を持つuniqe_ptrは、デフォルトの削除機能を使用する_unique_ptr<Base>_と互換性がありません。とにかく、_~Base_が仮想でない場合は、派生インスタンス。

私が行う個々の提案は、簡単にフォローでき、すべて一緒にフォローできるようになっています。彼らは、すべてのリソース管理をライブラリコンポーネントとコンパイラが生成したコードで実行できるようにすることで、より単純なコードを作成しようとします。

クラスで(仮想)デストラクタを定義すると、コンパイラが提供する移動コンストラクタ/代入演算子が禁止され、C++の将来のバージョンでコンパイラが提供するコピーコンストラクタ/代入演算子も禁止される可能性があります。それらを復活させることは_=default_で簡単になりましたが、それでも多くの定型コードのように見えます。そして、最良のコードは、間違ってはいけないので、書く必要のないコードです(私はそのルールにまだ例外があることを知っています)。

「(仮想)デストラクタを定義しないでください」を私の「ゼロのルール」の結果として要約すると、次のようになります。

最新のC++で多形(OO)クラス階層を設計し、そのインスタンスをヒープに割り当て、基本クラスポインターを介してそれらにアクセスする必要がある場合は、make_shared<Derived>()を使用してインスタンス化し、_shared_ptr<Base>_を使用してそれらを維持します。これにより、「ゼロのルール」を維持できます。

これは、あなたが必須すべてのポリモーフィックオブジェクトをヒープに割り当てるという意味ではありません。たとえば、_(Base&)_をパラメーターとして取る関数を定義すると、ローカルのDerived変数を使用して問題なく呼び出すことができ、Baseの仮想メンバー関数に関して多態的に動作します。

私の意見では、動的OOポリモーフィズムは、多くのシステムで頻繁に使用されています。ヒープが割り当てられたオブジェクトを使用した動的ポリモーフィズムが正しい解決策。

36
PeterSom

これは、プレゼンテーションの他の場所で言及されている「ゼロのルール」と関係があると思います。

自動メンバー変数しかない場合(つまり、生のポインターになるメンバーにはshared_ptrまたはunique_ptrを使用する)、独自のコピーまたは移動コンストラクター、または割り当て演算子を作成する必要はありません-コンパイラが提供するデフォルトが最適になります。クラス内初期化では、デフォルトのコンストラクターも必要ありません。そして最後に、仮想かどうかに関係なく、デストラクタを作成する必要はまったくありません。

2
Tristan Brindle

特定の質問に答えるには...

しかし、仮想メンバーがいる場合、仮想をデストラクタとして宣言することは、C++ 11では役に立たないのでしょうか?

仮想デストラクタの必要性は、C++ 11コア言語では変更されていません。ベースポインタを使用して派生オブジェクトを削除する場合は、デストラクタを仮想として宣言する必要があります。

スライドのステートメントは、C++ 11が仮想デストラクタに関する動作を何らかの形で変更したという印象を与えますが、そうではありません。著者が明らかにしたように、それはshared_ptrを使用する場合にのみ適用されます。しかし、仮想デストラクタがまだ必要であるという事実(shared_ptrの使用を除く)は、長い説明で薄められています。

0
ap-osd

リンクされた論文は、関連するコードを示しています。

std::unique_ptr<Derived> { new Derived };

保存される削除機能はstd::default_delete<Derived>であり、Base::~Baseが仮想である必要はありません。

これで、move thisをunique_ptr<Base>に移動できます。また、std::default_delete<Derived>に変換せずにstd::default_delete<Base>も移動します。

0
MSalters