1. ホーム
  2. c++

空の」コンストラクタやデストラクタは、生成されたものと同じことをするのでしょうか?

2023-09-02 18:29:32

質問

以下のような(おもちゃの)C++クラスがあるとします。

class Foo {
    public:
        Foo();
    private:
        int t;
};

デストラクタが定義されていないため、C++コンパイラは自動的にクラス Foo . デストラクタが動的に割り当てられたメモリをクリーンアップする必要がない場合 (つまり、コンパイラが提供するデストラクタに合理的に頼ることができる場合)、空のデストラクタを定義する、すなわち、デストラクタを定義する必要があります。

Foo::~Foo() { }

は、コンパイラが生成したものと同じことをするのでしょうか?空のコンストラクタはどうでしょう -- つまり Foo::Foo() { } ?

違いがあるとすれば、それはどこに存在するのでしょうか? そうでない場合、どちらかの方法が他よりも好ましいのでしょうか?

どのように解決するのですか?

同じことをする(要するに何もしない)ことになります。しかし、書かないのと同じではありません。なぜなら、デストラクタを書くには、ベースクラスのデストラクタが動作していることが必要だからです。もしベースクラスのデストラクタがprivateであったり、その他の理由で呼び出すことができないのであれば、あなたのプログラムは欠陥があることになります。次のように考えてみてください。

struct A { private: ~A(); };
struct B : A { }; 

B型(つまり暗黙のうちにA型)のオブジェクトを破壊する必要がない限り、これはOKです。例えば、動的に生成されたオブジェクトに対してdeleteを呼ばない、あるいはそもそもそのオブジェクトを生成しない場合などです。もしそうであれば、コンパイラは適切な診断を表示します。では、明示的に提供する場合

struct A { private: ~A(); };
struct B : A { ~B() { /* ... */ } }; 

これは暗黙のうちにベースクラスのデストラクタを呼び出そうとするもので、定義時にすでに ~B .

デストラクタの定義とメンバデストラクタへの暗黙の呼び出しを中心としたもう一つの違いがあります。このスマートポインタのメンバを考えてみましょう。

struct C;
struct A {
    auto_ptr<C> a;
    A();
};

型のオブジェクトを想定してみましょう。 C にある A のコンストラクタの定義で作成されます。 .cpp ファイルの中で作成され、そのファイルには構造体 C . さて、もしあなたが構造体 A を使用し A オブジェクトの破壊を要求する場合、コンパイラは上のケースと同じように暗黙のうちにデストラクタの定義を提供します。このデストラクタは暗黙のうちに auto_ptr オブジェクトのデストラクタを呼び出します。そしてそれは、それが保持している C オブジェクトを指すポインタを削除します。 C ! の中に現れた .cpp ファイルに表示され、そこで構造体 A のコンストラクタが定義されています。

これは実は、pimplイディオムを実装する際によくある問題です。ここでの解決策は、デストラクタを追加して、その空の定義を .cpp ファイルの中で、構造体 C が定義されているファイルです。そのメンバのデストラクタを呼び出すときに、構造体の定義がわかります。 C の定義を知り、そのデストラクタを正しく呼び出すことができます。

struct C;
struct A {
    auto_ptr<C> a;
    A();
    ~A(); // defined as ~A() { } in .cpp file, too
};

なお boost::shared_ptr はそのような問題はありません。その代わりに、そのコンストラクタが特定の方法で呼び出されたときに完全な型を要求します。

現在のC++で違いが出てくるもう一つの点は、コンストラクタで memset などを使いたい場合です。このような型はもはや POD (plain old data) ではないので、ビットコピーすることは許されません。次の C++ バージョンではこの状況が改善され、他のより重要な変更が行われない限り、このような型をまだビットコピーできるようになっています。


コンストラクタについて質問されたので。まあ、これらについても同じようなことが言えます。コンストラクタには、デストラクタへの暗黙の呼び出しも含まれることに注意してください。auto_ptr のようなものでは、これらの呼び出しは (実行時に実際に行われないとしても - 純粋な可能性はすでにここでは重要です) デストラクタと同じ害を及ぼし、コンストラクタ内の何かがスローするときに起こります - コンパイラはその後メンバのデストラクタを呼び出すことが要求されます。 この回答 はデフォルトコンストラクタの暗黙的な定義をいくらか使用しています。

また、上記のデストラクタについて述べた、可視性とPODネスについても同様です。

初期化に関して一つ重要な違いがあります。もしユーザが宣言したコンストラクタを置いた場合、その型はもうメンバの値の初期化を受け取らず、必要な初期化はすべてコンストラクタに任されます。例を挙げます。

struct A {
    int a;
};

struct B {
    int b;
    B() { }
};

この場合、常に次のようになります。

assert(A().a == 0);

以下は未定義の動作ですが、なぜなら b が初期化されなかったからです (コンストラクタがそれを省略したのです)。値はゼロかもしれませんが、他の奇妙な値であってもかまいません。このような初期化されていないオブジェクトから読み取ろうとすると、未定義の動作が発生します。

assert(B().b == 0);

これは、この構文を new のように new A() (最後の括弧に注意してください - もし括弧が省略された場合、値の初期化は行われず、初期化できるユーザが宣言したコンストラクタが存在しないためです。 a は初期化されないままとなります)。