web-dev-qa-db-ja.com

C ++コンストラクタ/デストラクタの継承

編集:回答の要約

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

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

  • コンストラクター
    • BはAからコンストラクターを継承しません。
    • Bのアクターが明示的にone of Aのアクターを呼び出さない限り、Aからのデフォルトのアクターは自動的に呼び出されますbefore Bのアクター本体(Bは作成した)。
  • デストラクタ
    • BはAのdtorを継承しません。
    • After終了すると、Bのデストラクタは自動的にAのデストラクタを呼び出します。

謝辞:特にオリチャールズワースとコスの回答に感謝したいと思います。コスの答えを解決策として設定しました。


オリジナルポスト

Googleで「C++ destructor inheritance site: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:では、サブクラスのコンストラクターを入力で呼び出すとどうなりますか?スーパークラスの「空のコンストラクター」も呼び出されますか?

55
Sheljohn

用語、用語...

OK、「Fooは継承されます」とはどういう意味ですか?クラスAのオブジェクトがインターフェースにFooを持っている場合、BのサブクラスであるクラスAのオブジェクトもFooのインターフェイス。

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

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

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

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

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

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

32
Kos

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

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


Q2:継承されていないと思う人は誰でも説明してください。

これは用語の定義の問題かもしれません。仮想デストラクタが存在し、「期待どおりに」動作することは明らかですが、C++標準([class.virtual])には次のように記載されています。

デストラクタが継承されない場合でも、派生クラスのデストラクタは、仮想と宣言された基本クラスのデストラクタをオーバーライドします

(エンファシス鉱山)


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

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

7

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

4
Pete Becker

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

この例は、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
}

~BDの継承メンバーではありませんでした。fの最初のステートメントの形式は正しくありません。現状では、非常に危険ですが、合法的なC++です。

3
aschepler

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

継承は、オブジェクトをクラスに埋め込むようなものです。

クラスが基本クラスを継承している場合、基本クラスのコンストラクタはcalledであり、次に派生クラスのものであり、デストラクタのcallは逆の順序です。

そのため、基本クラスコンストラクターが呼び出される理由(継承されていないものはパラメーター/デフォルトで呼び出される場合があります):派生クラスのコンストラクターの実行時に基本クラスが適切に構築されることを保証します。

Now Calling of Destructor(calling not inherit):ベースオブジェクトがスコープ外に出ると、デストラクタが単独で呼び出されるため、デストラクタの継承にnpの問題があります。

今あなたの質問:

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

3
sourcecode

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

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

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

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

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.
1
BigPeteB
I would want to express my thoughts. Creating any object is done in two stages:

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

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

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

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

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

0
Konstantin