71

編集:回答の要約

以下では、BはAのサブクラスです。

それは用語の問題です。Bのctor/dtorがAのインターフェースから借用されないという意味で、ctorsとdtorsは継承されません。クラスには少なくとも1つのコンストラクタがあり、デストラクタは1つだけです。

  • コンストラクター
    • BはAからコンストラクターを継承しません。
    • Bのctorが明示的にAのctorのいずれかを呼び出さない限り、AのデフォルトのctorはBのctor本体の前に自動的に呼び出されます(Bが作成される前にAを初期化する必要があるという考えです)。
  • デストラクタ
    • BはAのdtorを継承しません。
    • 終了後、Bのデストラクタは自動的にAのデストラクタを呼び出します。

謝辞: 特にOli CharlesworthとKosの回答に感謝します。私が最もよく理解したのは、Kosの回答を解決策として設定しました。


元の投稿

Googleで「C++デストラクタ継承サイト:stackoverflow.com」を検索すると、現在次の投稿が見つかります。

  1. コンストラクタとデストラクタの継承:3万人以上の評判を持つ2人のユーザーが、継承されていると言っていますが、そうではないと言っています
  2. 仮想デストラクタは継承されますか?:ここでは、デストラクタが継承されていないことを示すものは何も言及されていません
  3. C ++のデストラクタと継承?:コメントは、デストラクタが継承されていることを示しているようです

Q1:私も実践から知っていることですが、派生クラスのコンストラクターを明示的に定義しないと、親コンストラクターと同じプロトタイプで派生オブジェクトを初期化することはできません。それは正しいですか?


デストラクタが継承されているように見えることは投稿からかなり明らかですが、32kの評判を持つユーザーが継承されていないと言うという事実にはまだ戸惑っています。私はみんなの心を明確にするべき小さな例を書きました:

#include <cstdio>

/******************************/

// Base class
struct A
{
    A() { printf( "\tInstance counter = %d (ctor)\n", ++instance_counter ); }
    ~A() { printf( "\tInstance counter = %d (dtor)\n", --instance_counter ); }

    static int instance_counter;
};

// Inherited class with default ctor/dtor
class B : public A {};

// Inherited class with defined ctor/dtor
struct C : public A
{
    C() { printf("\tC says hi!\n"); }
    ~C() { printf("\tC says bye!\n"); }
};

/******************************/

// Initialize counter
int A::instance_counter = 0;

/******************************/

// A few tests
int main()
{
    printf("Create A\n"); A a;
    printf("Delete A\n"); a.~A();

    printf("Create B\n"); B b;
    printf("Delete B\n"); b.~B();

    printf("Create new B stored as A*\n"); A *a_ptr = new B();
    printf("Delete previous pointer\n"); delete a_ptr;

    printf("Create C\n"); C c;
    printf("Delete C\n"); c.~C();

}

そしてここに出力があります(g ++ 4.4.3でコンパイルされました):

Create A
    Instance counter = 1 (ctor)
Delete A
    Instance counter = 0 (dtor)
Create B
    Instance counter = 1 (ctor)
Delete B
    Instance counter = 0 (dtor)
Create new B stored as A*
    Instance counter = 1 (ctor)
Delete previous pointer
    Instance counter = 0 (dtor)
Create C
    Instance counter = 1 (ctor)
    C says hi!
Delete C
    C says bye!
    Instance counter = 0 (dtor)  // We exit main() now
    C says bye! 
    Instance counter = -1 (dtor)
    Instance counter = -2 (dtor)
    Instance counter = -3 (dtor)

Q2:受け継がれていないと思っている人は、説明してもらえますか?

Q3:では、入力を使用してサブクラスのコンストラクターを呼び出すとどうなりますか?スーパークラスの「空のコンストラクター」も呼び出されますか?

4

7 に答える 7

42

用語、用語..。

OK、「Foo isinherited」とはどういう意味ですか?AクラスのオブジェクトがFooそのインターフェースにある場合B、のサブクラスであるクラスのオブジェクトAもそのインターフェースにあることを意味しFooます。

  • コンストラクターはオブジェクトのインターフェースの一部ではありません。それらはクラスに直接属します。クラスABは、完全に異なるコンストラクターのセットを提供する場合があります。ここでは「継承される」ことはありません。

    実装の詳細:各BのコンストラクターはいくつかのAのコンストラクターを呼び出します。

  • デストラクタは、実際には各オブジェクトのインターフェイスの一部です。これは、オブジェクトのユーザーがデストラクタを呼び出す責任があるためです(つまりdelete、オブジェクトをスコープ外にすることで直接または間接的に)。各オブジェクトには、デストラクタが1つだけあります。それは、オプションで仮想デストラクタである可能性がある独自のデストラクタです。それは常にそれ自身のものであり、継承されません。

    (実装の詳細:BのデストラクタがAのデストラクタを呼び出します。)

つまり、ベースコンストラクタと派生コンストラクタおよびデストラクタの間には関係がありますが、「それらは継承されている」というわけではありません。

これがあなたが考えていることに答えることを願っています。

于 2013-01-06T17:07:42.813 に答える
7

Q1:私も実際に知っていることですが、派生クラスのコンストラクターを明示的に定義しないと、親コンストラクターと同じプロトタイプで派生オブジェクトを初期化することはできません。それは正しいですか?

スーパークラスでデフォルトのコンストラクターを定義したという些細な場合を除いて、そうです。


Q2:受け継がれていないと思っている人は、説明してもらえますか?

これは、用語の定義の問題である可能性があります。仮想デストラクタが存在し、「期待どおりに」機能することは明らかですが、C ++標準([class.virtual])では次のようになっています。

デストラクタは継承されませんが、派生クラスのデストラクタは、仮想として宣言された基本クラスのデストラクタをオーバーライドします。

(強調鉱山)


Q3:では、入力を使用してサブクラスのコンストラクターを呼び出すとどうなりますか?スーパークラスの「空のコンストラクター」も呼び出されますか?

特定のスーパークラスコンストラクターを明示的に呼び出さない場合は、デフォルトのスーパークラスコンストラクターが呼び出されます(表示されていると想定)。

于 2013-01-06T16:53:02.200 に答える
4

デストラクタは継承されません。クラスが1つを定義しない場合、コンパイラーは1つを生成します。デストラクタが基本クラスのデストラクタを呼び出すだけの些細なケースの場合、多くの場合、デストラクタの明示的なコード(継承を模倣する)がないことを意味します。ただし、クラスにデストラクタを持つメンバーがある場合、生成されたデストラクタは、基本クラスのデストラクタを呼び出す前に、それらのメンバーのデストラクタを呼び出します。これは、継承された関数では実行できないことです。

于 2013-01-06T17:11:03.533 に答える
4

継承とは、既存のクラスを変更せずに再利用および拡張して、それらの間に階層関係を作成するメカニズムです。

継承は、オブジェクトをクラスに埋め込むのとほとんど同じです。

クラスが基本クラスを継承している場合、基本クラスのコンストラクターが最初に呼び出され、次に派生クラスのコンストラクターが呼び出され、デストラクタの呼び出しは逆の順序になります。

したがって、基本クラスコンストラクターが呼び出される理由(継承されないと呼ばれるのはparameters / defaultを使用する場合があります):派生クラスのコンストラクターが実行されるときに基本クラスが適切に構築されることを保証します。

現在、デストラクタの呼び出し(継承ではない呼び出し):ベースオブジェクトがスコープから外れると、デストラクタが独自に呼び出されます。したがって、デストラクタの継承のnp問題があります。

今あなたの質問:

ans1-はいあなたは最初の質問に正解です。
ans 2-したがって、オブジェクトのスコープがなくなった後、デストラクタは継承されずに呼び出されます。
ans 3- 派生クラスでパラメーターを使用して呼び出しを行う場合、そのコンストラクターのみが呼び出され、他のコンストラクターは呼び出されません。
オブジェクトの作成時にコンストラクターが呼び出されるのと同じオブジェクトの2つのコンストラクターがオブジェクトの作成時に呼び出されるという問題はありません。新しいオブジェクトを使用できるように準備します。したがって、異なるコンストラクターを使用してオブジェクトを2回準備するロジックはありません。

于 2013-01-06T17:18:35.577 に答える
4

技術的には、デストラクタは継承されます。ただし、通常の状況では、継承されたデストラクタは派生クラスに直接使用されません。これらは、派生クラス自体のデストラクタが、より大きなオブジェクトを破棄するステップとして、独自の「基本クラスサブオブジェクト」を破棄するために呼び出すために呼び出されます。また、派生オブジェクトで基本クラスのデストラクタを直接使用するという異常な状況では、未定義動作を回避することは非常に困難です。

この例は、C ++標準(12.4p12)から直接引用したものです。

struct B {
  virtual ~B() { }
};
struct D : B {
  ~D() { }
};

D D_object;
typedef B B_alias;
B* B_ptr = &D_object;

void f() {
  D_object.B::~B();              // calls B's destructor
  B_ptr->~B();                   // calls D's destructor
  B_ptr->~B_alias();             // calls D's destructor
  B_ptr->B_alias::~B();          // calls B's destructor
  B_ptr->B_alias::~B_alias();    // calls B's destructor
}

~Bの継承されたメンバーでない場合D、の最初のステートメントはf不正な形式になります。非常に危険ですが、現状では合法的なC++です。

于 2013-01-06T21:21:15.620 に答える
2

この例では、デストラクタ関数を明示的に呼び出しています。これは合法ですが(コンパイルして実行したため)、ほとんどの場合正しくありません。

で作成された動的に割り当てられたオブジェクトの場合、オブジェクトが。newで削除されたときにデストラクタが実行されdeleteます。

関数のスコープ内でオブジェクトを宣言するだけで作成される静的に割り当てられたオブジェクトの場合、オブジェクトのスコープが消えたときにデストラクタが実行されます。つまり、main()終了すると、オブジェクトのデストラクタが実行されます。ただし、これらのオブジェクトを手動で呼び出すことにより、これらのオブジェクトのデストラクタをすでに実行しています。これが、例の出力がカウントが-3に減少していることを示している理由です...、、、および2回のデストラクタを実行しaましbc

これは同じコードで、デストラクタが自動的に実行されるタイミングを示すために注釈が付けられています。

int main()
{
    printf("Create A\n"); A a;
    printf("Delete A\n"); a.~A();

    printf("Create B\n"); B b;
    printf("Delete B\n"); b.~B();

    printf("Create new B stored as A*\n"); A *a_ptr = new B();
    printf("Delete previous pointer\n");
    delete a_ptr;   // Implicitly calls destructor for a_ptr.  a_ptr is class B,
       // so it would call a_ptr->~B() if it existed. Because B is an A, after
       // its destructor is called, it calls the superclass's destructor,
       // a_ptr->~A().

    printf("Create C\n"); C c;
    printf("Delete C\n"); c.~C();
}
// Function exits here at the close brace, so anything declared in its scope is
// deallocated from the stack and their destructors run.
// First `c` is destroyed, which calls c.~C(), then because C is a subclass of A
// calls c.~B() (which doesn't exist, so a blank implementation is used), then
// because B is a subclass of A calls c.~A().  This decrements the counter, but
// the count is wrong because you already manually called c.~C(), which you
// ordinarily shouldn't have done.
// Then `b` is destroyed, in a similar manner.  Now the count is off by 2,
// because you had already called b.~B().
// Lastly `a` is destroyed, just as above.  And again, because you had already
// called a.~A(), the count is now off by 3.
于 2014-03-28T18:04:28.733 に答える
0
I would want to express my thoughts. Creating any object is done in two stages:

1.オブジェクトにメモリ領域を割り当てます。

  1. このメモリ領域を初期化しています。

    オブジェクトのコンストラクターは、(このオブジェクトの)クラスの関数(メソッド)であり、割り当てられたメモリ領域を初期化し、自動的に呼び出されます。継承とは、あるクラスのオブジェクトを他のクラスのオブジェクトに埋め込むことです。「これ」「ふたの下」のポイナーとの戯れがあります。「this」は、クラスのメソッドに暗黙的に渡されます。

    コード「Bb」が実行されたときに何が起こっているのか。まず、メモリの領域がオブジェクトbに割り当てられます。クラスBには、このメモリを初期化するために自動的に呼び出される独自のデフォルトコンストラクタB()があります。B()は関数であるため、スタックフレームは機能するために作成されます。このコンストラクターのアドレスはb(暗黙)です。ただし、Aのオブジェクトはオブジェクトbに埋め込まれている必要があります。Aのオブジェクトには名前がありません。Bのコンストラクターは、Aのnoname埋め込みオブジェクトも作成する必要があることを認識しています(コンパイラーC ++が機能するように)。したがって、クラスAのnoname埋め込みオブジェクトを初期化するためのクラスAのコンストラクターが、Bのコンストラクターで呼び出されます。新しいスタックフレームが呼び出され、nonameオブジェクトが初期化されます。その後、スタックフレームが閉じられ、クラスBのオブジェクトbが実行されます。

    デストラクタはクラスのメソッドでもあります。〜B()を呼び出すと、bは破棄されません。デストラクタは、オブジェクトが破棄されているときに仮想的に呼び出される関数です。ただし、デストラクタを呼び出すときにオブジェクトを破棄する必要があるという意味ではありません。Bのデストラクタが呼び出されると、1つのスタックフレームが作成されます。Bのデフォルトの記述子は、クラスAのnoname埋め込みオブジェクトを認識しています(したがって、コンパイラC ++は機能します)。したがって、デストラクタはAのデストラクタを呼び出します。

于 2018-11-21T20:41:59.907 に答える