web-dev-qa-db-ja.com

「空の」コンストラクターまたはデストラクターは、生成されたものと同じことを行いますか?

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

_class Foo {
    public:
        Foo();
    private:
        int t;
};
_

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

_Foo::~Foo() { }
_

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

違いがある場合、どこに存在しますか?そうでない場合、一方の方法が他方よりも優先されますか?

72
Andrew Song

同じことをします(本質的には何もしません)。しかし、それを書いていない場合と同じではありません。デストラクタを作成するには、動作する基本クラスのデストラクタが必要になるためです。基本クラスのデストラクタがプライベートである場合、または呼び出すことができない他の理由がある場合、プログラムは故障しています。このことを考慮

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

動的に作成されたオブジェクトでdeleteを呼び出したり、最初の場所。その場合、コンパイラーは適切な診断を表示します。明示的に提供する場合

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

その1つはベースクラスのデストラクタを暗黙的に呼び出すことを試み、_~B_の定義時にすでに診断を引き起こします。

デストラクタの定義とメンバーデストラクタへの暗黙的な呼び出しを中心とした別の違いがあります。このスマートポインターメンバーを検討してください

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

タイプCのオブジェクトが、構造体Cの定義も含む_.cpp_ファイルのAのコンストラクターの定義で作成されると仮定します。 struct Aを使用し、Aオブジェクトの破棄が必要な場合、コンパイラーは上記の場合と同様に、デストラクターの暗黙的な定義を提供します。そのデストラクタは、auto_ptrオブジェクトのデストラクタも暗黙的に呼び出します。そして、Cの定義を知らなくても、Cオブジェクトを指す、保持しているポインターを削除します!これは、構造体Aのコンストラクターが定義されている_.cpp_ファイルに現れました。

これは、実際にpimplイディオムを実装する際の一般的な問題です。ここでの解決策は、構造体Cが定義されている_.cpp_ファイルにデストラクタを追加し、空の定義を提供することです。メンバのデストラクタを呼び出すときに、構造体Cの定義を認識し、デストラクタを正しく呼び出すことができます。

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

_boost::shared_ptr_にはその問題がないことに注意してください:コンストラクターが特定の方法で呼び出される場合、代わりに完全な型が必要です。

現在のC++で違いを生じるもう1つの点は、デストラクタを宣言したユーザーを持つそのようなオブジェクトでmemsetとその友人を使用する場合です。そのようなタイプはもはやPODではなく(プレーンな古いデータ)、これらはビットコピーできません。この制限は実際には必要ないことに注意してください。次のC++バージョンではこの状況が改善されているため、他の重要な変更が行われない限り、このような型をビットコピーできます。


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

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

初期化に関して重要な違いが1つあります。ユーザーが宣言したコンストラクターを配置すると、型はメンバーの値の初期化を受け取らず、必要な初期化を行うのはコンストラクター次第です。例:

_struct A {
    int a;
};

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

この場合、次のことが常に当てはまります

_assert(A().a == 0);
_

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

_assert(B().b == 0);
_

これは、new A()のようなnewでこの構文を使用する場合にも当てはまります(末尾の括弧に注意してください-省略された場合、値の初期化は行われず、ユーザー宣言コンストラクタもありませんそれはそれを初期化する可能性があり、aは初期化されないままになります)。

116

私は議論の後半にいることを知っていますが、私の経験では、コンパイラが生成したものと比較して空のデストラクタに直面するとコンパイラの動作が異なると言います。少なくともこれは、MSVC++ 8.0(2005)およびMSVC++ 9.0(2008)の場合です。

式テンプレートを使用するコード用に生成されたアセンブリを見ると、リリースモードではBinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)への呼び出しがインライン化されないことに気付きました。 (正確なタイプとオペレーター署名には注意を払わないでください)。

問題をさらに診断するために、さまざまな デフォルトでオフになっているコンパイラ警告 を有効にしました。 C4714 警告は特に興味深いものです。 ___forceinline_でマークされた関数がそれでもインライン化されない場合にコンパイラーから出力されます。

C4714警告を有効にし、___forceinline_で演算子をマークし、コンパイラーが演算子への呼び出しをインライン化できないことを報告することを確認できました。

ドキュメントに記載されている理由の中で、コンパイラーは___forceinline_でマークされた関数のインライン化に失敗します。

-GX/EHs/EHaがオンの場合、値によって巻き戻し不可能なオブジェクトを返す関数

これは私のBinaryVectorExpression operator + (const Vector& lhs, const Vector& rhs)の場合です。 BinaryVectorExpressionは値によって返され、デストラクタが空であっても、この戻り値は巻き戻し不可能なオブジェクトと見なされます。 throw ()をデストラクタに追加しても、コンパイラと とにかく例外仕様の使用は避けます は助けになりませんでした。空のデストラクタをコメントアウトすると、コンパイラはコードを完全にインライン化できます。

重要なことは、これからすべてのクラスで、空のデストラクタが意図的に何もしないことを人間に知らせるために、空のデストラクタをコメントアウトすることです。デストラクタがスローできないことを示します。

_//~Foo() /* throw() */ {}
_

お役に立てば幸いです。

17
Gregory Pakosz

クラス外で定義した空のデストラクタは、ほとんどの点で同様のセマンティクスを持ちますが、すべてではありません。

具体的には、暗黙的に定義されたデストラクタ
1)はインラインパブリックメンバーです(あなたのものはインラインではありません)
2)は、些細なデストラクタとして示されています(単純な型を作成する必要がありますが、結合することはできません)
3)には例外仕様があります(throw()、あなたのものはありません)

12
Faisal Vali

はい、その空のデストラクタは自動生成されたものと同じです。私は常にコンパイラーにそれらを自動的に生成させてきました。何か特別なことをする必要がない限り、デストラクタを明示的に指定する必要はないと思います。たとえば、仮想またはプライベートにします。

8
David Seiler

私はデビッドに同意しますが、仮想デストラクタ、つまり.

virtual ~Foo() { }

fooクラスを継承する人は、デストラクタが呼び出されないことに気付いていない可能性があるため、仮想デストラクタが欠落するとメモリリークが発生する可能性があります。

3
oscarkuo

空の宣言を置くことをお勧めします。それは将来のメンテナーにそれが見落としではないことを伝え、あなたは本当にデフォルトの宣言を使用するつもりでした。

1
Ape-inago

定義を参照できるため、空の定義で問題ありません

virtual ~GameManager() { };
仮想〜GameManager();
仮想デストラクタの定義なし
Undefined symbols:
  "vtable for GameManager", referenced from:
      __ZTV11GameManager$non_lazy_ptr in GameManager.o
      __ZTV11GameManager$non_lazy_ptr in Main.o
ld: symbol(s) not found
0
MLRUS