C++ コア ガイドライン:デストラクタ ルール

私のクラスにはデストラクタが必要ですか?この質問をよく耳にしました。ほとんどの場合、答えはノーであり、ゼロのルールで問題ありません。答えがイエスで、5 のルールに戻ることもあります。より正確には。ガイドラインでは、デストラクタに関する 8 つのルールが提供されています。

8 つのルールは次のとおりです。

  • C.30:クラスがオブジェクトの破棄時に明示的なアクションを必要とする場合は、デストラクタを定義してください
  • C.31:クラスによって取得されたすべてのリソースは、クラスのデストラクタによって解放される必要があります
  • C.32:クラスに生のポインタがある場合 (T* ) または参照 (T& )、所有している可能性があるかどうかを検討してください
  • C.33:クラスに所有ポインター メンバーがある場合は、 を定義します。 デストラクタ
  • C.34:クラスに所有参照メンバーがある場合は、 を定義します。 デストラクタ
  • C.35:基本クラスのデストラクタは、public で virtual、または protected で nonvirtual にする必要があります
  • C.36:デストラクタは失敗しないかもしれません
  • C.37:デストラクタを noexcept にする

それぞれについて詳しく見ていきましょう。

デストラクタ ルール:

C.30:クラスがオブジェクトの破棄時に明示的なアクションが必要

オブジェクトのデストラクタがその存続期間の終わりに自動的に呼び出されることは、C++ の特徴です。より正確には、オブジェクトがスコープ外になると、オブジェクトのデストラクタが呼び出されます。この完全に決定論的な動作により、非常に重要なリソースをデストラクタで解放できます。

C++ のロックまたはスマート ポインターは、この特性を使用します。どちらも、範囲外になると、基になるリソースを自動的に解放します。

void func(){
 std::unique_ptr<int> uniqPtr = std::make_unique<int>(2011);
 std::lock_guard<std::mutex> lock(mutex);
 . . .
} // automatically released

unipPtr はその int を解放し、そのミューテックスをロックします。どちらも RAII イディオム (Resource Acquisition Is Initialization) に従います。 RAII に興味がある場合は、私の投稿 Garbage Collection - No Thanks に RAII に関する Bjarne Stroustrup のコメントが含まれています。

ルールを逆に読むこともできます。クラスのすべてのメンバーにデフォルトのデストラクタがある場合は、独自のデストラクタを定義しないでください。

class Foo { // bad; use the default destructor
public:
 // ...
 ~Foo() { s = ""; i = 0; vi.clear(); } // clean up
private:
 string s;
 int i;
 vector<int> vi;
};

C.31:すべてのリソースはクラスは、クラスのデストラクタによって解放される必要があります

このルールは非常に明白に聞こえますが、リソース リークを防ぐのに役立ちます。右?ただし、どのクラス メンバーがデフォルト操作の完全なセットを持っているかを考慮する必要があります。ここで、もう一度ゼロまたは 5 のルールに戻ります。

おそらく、クラス File には std::ifstream とは対照的にデストラクタがないため、MyClass のインスタンスがスコープ外になるとメモリ リークが発生する可能性があります。

class MyClass{
 std::ifstream fstream; // may own a file
 File* file_; // may own a file
 ... 
};

Zbigniew Dubil は、ルールをより具体的にする必要があると述べました:すべてのリソースが 所有 クラスのデストラクタによって解放される必要があります。クラスには、そのクライアントのオブジェクトを作成するファクトリを含めることができるため、彼は正しいです。クラスのデストラクタがオブジェクトを解放する必要はありません。

C.32:クラスに生のポインタがある場合 (T* ) または参照 (T& )、所有している可能性があるかどうかを検討してください

クラスに生のポインターまたは参照がある場合、答えなければならない質問があります。所有者は誰ですか?クラスが所有者である場合は、リソースを削除する必要があります。

C.33:クラスに所有ポインター メンバーがある場合は、 デストラクタ

C.34:クラスに所有参照メンバーがある場合、define または デストラクタ

ルール C.33 と C.34 は非常に簡単に言い換えることができます。ポインターまたは参照を所有している場合は、std::unique_ptr などのスマート ポインターのみを使用します。 std::unique_ptr は設計上、生のポインターと同じくらい効率的です。したがって、時間やメモリのオーバーヘッドはなく、付加価値のみがあります。 C++ のスマート ポインターの詳細に関する私の投稿を次に示します。

C.35:基本クラスのデストラクタパブリックで仮想、または保護された非仮想のいずれかである必要があります

この規則は、仮想関数を持つクラスにとって非常に興味深いものに思えます。 2 つの部分に分けましょう。

パブリックおよび仮想デストラクタ

クラスにパブリック デストラクタと仮想デストラクタがある場合、基本クラス ポインタを介して派生クラスのインスタンスを破棄できます。参照についても同じことが言えます。

struct Base { // no virtual destructor
 virtual void f(){};
};

struct Derived : Base {
 string s {"a resource needing cleanup"};
 ~D() { /* ... do some cleanup ... */ }
};

...

Base* b = new Derived();
delete b;

コンパイラは Base に対して非仮想デストラクタを生成しますが、Base のデストラクタが非仮想の場合、Base ポインタを介して Derived のインスタンスを削除すると、未定義の動作になります。

保護された非仮想デストラクタ

これは非常に簡単に入手できます。基本クラスのデストラクタが保護されている場合、基本クラス ポインタを使用して派生オブジェクトを破棄することはできません。したがって、デストラクタは仮想であってはなりません。

型 (ポインタや参照ではない) についての要点を明確にするためだけに:

  • クラス Base のデストラクタが private の場合、その型は使用できません。
  • クラス Base のデストラクタが保護されている場合、Derived を Base から派生させ、Derived を使用することしかできません。
struct Base{
 protected:
 ~Base() = default;
};

struct Derived: Base{};

int main(){
 Base b; // Error: Base::~Base is protected within this context
 Derived d;
}

Base b を呼び出すとエラーが発生します。

C.36:デストラクタは失敗しない

C.37:デストラクタを noexcept にする

C.36 と C.37 に適用される規則は、非常に一般的です。デストラクタは失敗してはならないため、noexcept として宣言する必要があります。 noexcept について少し説明する必要があると思います。

  • 例外: デストラクタなどの関数を noexcept として宣言すると、この関数でスローされる例外は std::terminate を呼び出します。 std::terminate は現在インストールされている std::terminate_handler を呼び出します。これはデフォルトでは std::abort であり、プログラムは中止されます。関数 void func() noexcept; を宣言することによって。 noexcept として次のように述べています:
    • 関数が例外をスローしません。
    • 関数が例外をスローしても気にせず、プログラムを中止させます。

デストラクタを noexcept として明示的に宣言する必要がある理由は明らかです。デストラクタが失敗する可能性がある場合、エラーのないコードを記述する一般的な方法はありません。クラスのすべてのメンバーに noexcept デストラクタがある場合、ユーザー定義またはコンパイラ生成のデストラクタは暗黙的に noexcept になります。

次のステップ

少し奇妙に聞こえるかもしれませんが、デストラクタのルールの後にコンストラクタのルールが続きます。 C++ コア ガイドラインには約 10 のルールがあり、それらについては次の投稿で説明します。

詳細情報

  • RAII (Resource Acquisition Is Initialization):ガベージ コレクション - いいえ
  • 0 または 5 のルール:0 の 5 のルール
  • C++ のスマート ポインター:スマート ポインター