基本クラスの破壊者は通常仮想でなければならないことは誰もが知っています。しかし、派生クラスのデストラクタはどうですか? C++ 11では、キーワード「オーバーライド」とデフォルトのデストラクタを明示的に使用する機能があります。
struct Parent
{
std::string a;
virtual ~Parent()
{
}
};
struct Child: public Parent
{
std::string b;
~Child() override = default;
};
Childクラスのデストラクタでキーワード「override」と「= default」の両方を使用するのは正しいですか?この場合、コンパイラは正しい仮想デストラクタを生成しますか?
はいの場合、それは良いコーディングスタイルであると考えることができます。そして、基底クラスのデストラクタが仮想であることを保証するために、常にこのように派生クラスのデストラクタを宣言する必要がありますか?
Childクラスのデストラクタでキーワード「override」と「= default」の両方を使用するのは正しいですか?この場合、コンパイラは正しい仮想デストラクタを生成しますか?
はい、正しいです。正常なコンパイラでは、コードがエラーなしでコンパイルされる場合、このデストラクタ定義は何もしません。そのデストラクタ定義は、コードの動作を変更してはなりません。
良いコーディングスタイルだと思いますか
それは好みの問題です。私にとっては、基本クラスタイプがテンプレート化されている場合にのみ意味があります。その場合、仮想クラスは仮想クラスのデストラクタを持つという要件を強制します。そうでなければ、基本型が固定されている場合、そのようなコードはノイズであると考えます。基本クラスが魔法のように変わるわけではありません。 しかし壊れている可能性のあるものに依存するコードをチェックせずに物事を変更したい行き詰まったチームメイトがある場合、デストラクタ定義を追加の保護層として残すことをお勧めします。
override
はセーフティネットにすぎません。子クラスのデストラクタは、基本クラスのデストラクタが仮想である場合、どのように宣言されているか、またはまったく宣言されていない(つまり、暗黙的に宣言されたものを使用する)場合は常に仮想になります。
ここでoverride
を使用する理由は(少なくとも)1つあります。基本クラスのデストラクタが常に仮想であることを保証します。派生クラスのデストラクタが何かをオーバーライドしていると考えている場合、コンパイルエラーになりますが、オーバーライドするものはありません。また、生成されたドキュメントを残すための便利な場所も提供します。
一方で私はこれをしない2つの理由を考えることができます:
ヘッダーでデストラクターを定義する場合(またはインラインで作成する場合)、奇妙なコンパイルエラーが発生する可能性があります。クラスが次のように見えるとしましょう:
struct derived {
struct impl;
std::unique_ptr<derived::impl> m_impl;
~derived() override = default;
};
デストラクタ(ここではクラスとインラインになっている)が不完全なクラスderived::impl
のデストラクタを探しているため、コンパイラエラーが発生する可能性があります。
これは、コードのすべての行が不利になる可能性があるという私の言い回しです。機能的に何もしない場合は、単に何かをスキップするのが最善です。親クラスから基本クラスに仮想デストラクタを本当に強制する必要がある場合、誰かがstatic_assert
をstd::has_virtual_destructor
と組み合わせて使用することを提案しました。これにより、より一貫した結果が得られます。
「オーバーライド」はデストラクタに対して誤解を招くようなものだと思います。仮想関数をオーバーライドすると、それが置き換えられます。デストラクタは連鎖しているため、デストラクタを文字通りオーバーライドすることはできません
CppCoreGuidelines C.128 によると、派生クラスのデストラクタはvirtual
またはoverride
として宣言しないでください。
基本クラスのデストラクターが仮想と宣言されている場合、派生クラスのデストラクター
virtual
またはoverride
の宣言は避けてください。一部のコードベースとツールは、デストラクタのオーバーライドを要求する場合がありますが、これらのガイドラインの推奨事項ではありません。
CPPリファレンス は、override
が関数がvirtual
であることを確認し、実際に仮想関数をオーバーライドすることを示します。したがって、override
キーワードは、デストラクタが仮想であることを確認します。
override
を指定して= default
を指定しないと、リンカーエラーが発生します。
何もする必要はありません。 Child
dtorを未定義のままにすると問題なく動作します。
#include <iostream>
struct Notify {
~Notify() { std::cout << "dtor" << std::endl; }
};
struct Parent {
std::string a;
virtual ~Parent() {}
};
struct Child : public Parent {
std::string b;
Notify n;
};
int main(int argc, char **argv) {
Parent *p = new Child();
delete p;
}
それはdtor
を出力します。ただし、Parent::~Parent
でvirtual
を削除すると、コメントで指摘されているように、未定義の動作であるため、何も出力されません。
良いスタイルは、Child::~Child
にまったく言及しないことです。基本クラスが仮想と宣言したことを信頼できない場合は、override
および= default
を使用した提案が機能します。これらのデストラクタ宣言でコードを散らかす代わりに、それを保証するより良い方法があることを願っています。
デストラクタは継承されませんが、派生クラスの仮想デストラクタがベースクラスのデストラクタをオーバーライドすることは、規格に明確に記載されています。
C++標準(10.3仮想関数)から
6デストラクタは継承されませんが、派生クラスのデストラクタオーバーライド仮想クラスのデストラクタが宣言されました。 12.4および12.5を参照してください。
一方、また書かれています(9.2クラスメンバー)
8 virt-specifier-seqには、各virt-specifierを最大1つ含める必要があります。 virt-specifier-seqは、仮想メンバー関数(10.3)の宣言にのみ表示されます。
デストラクタは特別なメンバー関数のように呼び出されますが、デストラクタはメンバー関数でもあります。
デストラクタにvirt-specifier override
が含まれるかどうかが明確になるように、C++標準を編集する必要があります。現時点では明確ではありません。