コンストラクターが継承されないのはなぜですか?


33

コンストラクターが基本クラスから継承された場合、どのような問題が発生する可能性があるのか​​、私は混乱しています。Cpp Primer Plusによると、

コンストラクターは他のクラスメソッドとは異なり、新しいオブジェクトを作成しますが、他のメソッドは既存のオブジェクトによって呼び出されますこれが、コンストラクターが継承されない理由の1つです。継承とは、派生オブジェクトが基本クラスメソッドを使用できることを意味しますが、コンストラクターの場合、コンストラクターが作業を完了するまでオブジェクトは存在しません。

オブジェクトの構築が完了する前にコンストラクターが呼び出されることを理解しています。

子クラスが継承する場合、どうすれば問題につながる可能性があります(継承することで、子クラスが親クラスメソッドをオーバーライドできることを意味します

オブジェクトを作成するときを除いて、コード内から明示的にコンストラクターを呼び出す必要はないことを理解しています(まだ気づいていません)。その場合でも、何らかのメカニズムを使用して、親コンストラクタを呼び出すことができます[cppでは、使用::または使用member initialiser list、javaでは使用super]。Javaでは、1行目で呼び出す強制があります。最初に親オブジェクトを作成してから、子オブジェクトの構築を続行することを理解しています。

それを無効にすることができます。しかし、私はこれが問題を引き起こす可能性がある状況を思い付くことができません。子が親コンストラクタを継承しない場合、何が問題になる可能性がありますか?

これは、不要な関数を継承しないようにするためです。それとももっとありますか?


C ++ 11で実際にスピードアップしていませんが、 何らかの形でコンストラクターを継承していると思います
ヤニス

3
コンストラクタで何をするにしても、デストラクタで注意する必要があることに注意してください。
マノジR

2
「コンストラクタを継承する」という意味を定義する必要があると思います。すべての答えには、「コンストラクターを継承する」という意味のバリエーションがあります。それらのどれも私の最初の解釈と一致しないと思います。上記の「継承」は、派生オブジェクトが基本クラスメソッドを使用できることを意味する「灰色のボックス内」の説明は、コンストラクタが継承されることを意味します。派生オブジェクトは、常に基本クラスコンストラクターメソッドを常に使用できます。
ダンク

1
コンストラクターを継承することを明確にしたおかげで、いくつかの答えを得ることができます。
ダンク

回答:


41

派生クラスのコンストラクターは、基本クラスコンストラクターが行う必要のない追加のアクションを実行する必要があるため、C ++のコンストラクターを適切に継承することはできません。これらの追加のアクションは、派生クラスのデータメンバーの初期化です(そして、典型的な実装では、派生クラスvtableを参照するようにvpointerも設定します)。

クラスが構築されるとき、常に発生する必要がある多くのことがあります:基本クラス(存在する場合)のコンストラクターと直接メンバーを呼び出す必要があり、仮想関数がある場合、vpointerを正しく設定する必要があります。クラスのコンストラクターを提供しない場合、コンパイラー必要なアクションを実行するもののみを作成します。あなたがいる場合行うコンストラクタを提供していますが、(例えば、いくつかのメンバーの初期化)必要なアクションのいくつかを逃し、その後、コンパイラが自動的にあなたのコンストラクタに不足しているアクションを追加します。このようにして、コンパイラは各クラスに少なくとも1つのコンストラクターがあり、各コンストラクターが作成するオブジェクトを完全に初期化するようにします。

C ++ 11では、「コンストラクター継承」の形式が導入されました。このクラスでは、基本クラスのコンストラクターと同じ引数を取り、引数を単に転送する一連のコンストラクターを生成するようにコンパイラーに指示できます。基本クラス。
正式には継承と呼ばれていますが、派生クラス固有の関数がまだあるため、そうではありません。現在は、明示的に作成されるのではなく、コンパイラによって生成されます。

この機能は次のように機能します。

struct Base {
    Base(int a) : i(a) {}
    int i;
};

struct Derived : Base {
    Derived(int a, std::string s) : Base(a), m(s) {}

    using Base::Base; // Inherit Base's constructors.
    // Equivalent to:
    //Derived(int a) : Base(a), m() {}

    std::string m;
};

Derived現在、2つのコンストラクターがあります(コピー/移動コンストラクターはカウントしません)。1つはintと文字列を受け取り、もう1つはintのみを受け取ります。


だから、これは子クラスが独自のコンストラクタを持たないと仮定していますか?そして、継承されたコンストラクタは明らかに、実行される
べき子

C ++は、クラスが独自のコンストラクターを持たないように定義されています。だからこそ、「コンストラクターの継承」を実際継承とは考えないのです。退屈な定型文を書くのを避けるための方法にすぎません。
バートヴァンインゲンシェナウ

2
混乱は、空のコンストラクターでも舞台裏で動作するという事実に起因すると思います。そのため、コンストラクターには実際に2つの部分があります。内部動作とユーザーが作成する部分です。それらは十分に分離されていないため、コンストラクタは継承されません。
サリエン

1
どこm()から来たのか、タイプがの場合の変更方法を示すことを検討してくださいint
デュプリケータ

using Base::Base暗黙的に行うことは可能ですか?私は、派生クラスでその行を忘れてしまったと私はすべての派生クラスでコンストラクタを継承する必要がある場合には、大きな影響を持っているでしょう
ポストセルフ

7

「親コンストラクタを継承する」とはどういう意味かは明確ではありません。オーバーライドという言葉使用します。これは、多態的な仮想関数のように振る舞うコンストラクタについて考えているかもしれないことを示唆しています。「仮想コンストラクター」という用語はコードパターンの共通名なので、意図的に使用しません別のインスタンスを作成するためにオブジェクトの既存のインスタンスを実際に必要とする。

「仮想コンストラクター」パターン以外のポリモーフィックコンストラクターにはほとんど実用性がなく、実際のポリモーフィックコンストラクターを使用できる具体的なシナリオを見つけるのは困難です。リモートで有効なC ++でもない、非常に不自然な例:

struct Base {
  virtual Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) override {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Base(p1, p2); // This might call Derived(unsigned, unsigned).
  Derived *p_d2 = nullptr;
  p_d2 = new Base(p1, p2); // This might call Derived(unsigned, unsigned) too.
}

この場合、呼び出されるコンストラクターは、構築または割り当てられる変数の具象型に依存します。解析/コード生成中に検出するのは複雑であり、ユーティリティはありません。構築している具象型を知っていて、派生クラスの特定のコンストラクタを作成しました。次の有効な C ++コードはまったく同じで、わずかに短く、動作がより明確です。

struct Base {
  Base(unsigned p1, unsigned p2) {...}
};

struct Derived: public Base {
  Derived(unsigned p1, unsigned p2) : Base(p1, p2) {...}
};

int main(void) {
  unsigned p1 = 0;
  unsigned p2 = 42;
  Derived *p_d1 = new Derived(p1, p2); 
  Derived *p_d2 = nullptr;
  p_d2 = new Derived(p1, p2);
}


2番目の解釈またはおそらく追加の質問-明示的に非表示にしない限り、すべての派生クラスにBaseクラスコンストラクターが自動的に存在した場合はどうなりますか。

子が親コンストラクタを継承しない場合、何が問題になる可能性がありますか?

派生クラスの構築に使用するのが正しくない親コンストラクターを非表示にするには、追加のコードを記述する必要があります。これは、特定のパラメーターが無関係になるように、派生クラスが基本クラスを特殊化するときに発生する可能性があります。

典型的な例は長方形と正方形です(正方形と長方形は一般にリスコフ置換可能ではないため、あまり良い設計ではありませんが、問題を強調しています)。

struct Rectangle {
  Rectangle(unsigned width, unsigned height) {...}
};

struct Square : public Rectangle {
  explicit Square(unsigned side) : Rectangle(side, side) {...}
};

SquareがRectangleの2値コンストラクターを継承している場合、異なる高さと幅の正方形を構築できます...これは論理的に間違っているため、そのコンストラクターを非表示にする必要があります。


3

コンストラクターが継承されない理由:答えは驚くほど簡単です:基本クラスのコンストラクターは基本クラスを「構築」し、継承クラスのコンストラクターは継承クラスを「構築」します。継承されたクラスがコンストラクターを継承する場合、コンストラクターはベースクラスタイプのオブジェクトをビルドしようとし、継承クラスタイプのオブジェクトを「ビルド」することはできません。

どのような種類のクラスを継承する目的を無効にします。


3

派生クラスが基本クラスコンストラクターをオーバーライドできるようにすることで最も明らかな問題は、派生クラスの開発者が、その基本クラスを構築する方法を知る責任があることです。派生クラスが基本クラスを適切に構築しないとどうなりますか?

また、ベースクラスが他の派生型と適切にまたは互換性を持って構築されたという保証がないため、ベースクラスオブジェクトのコレクションが相互に互換性があると頼ることができなくなるため、リスコフ置換の原則は適用されなくなります。

1レベル以上の継承が追加されると、さらに複雑になります。ここで、派生クラスは、チェーンのすべての基本クラスを構築する方法を知る必要があります。

次に、継承階層の最上部に新しい基本クラスを追加するとどうなりますか?すべての派生クラスコンストラクターを更新する必要があります。


2

コンストラクターは他のメソッドと根本的に異なります。

  1. 記述しない場合に生成されます。
  2. すべての基本クラスコンストラクターは、手動で実行しなくても暗黙的に呼び出されます
  3. それらを明示的に呼び出すのではなく、オブジェクトを作成して呼び出します。

では、なぜそれらは継承されないのでしょうか?簡単な答え:手動で生成または作成されたオーバーライドが常に存在するため。

すべてのクラスにコンストラクターが必要なのはなぜですか?それは複雑な質問であり、その答えはコンパイラに依存していると思います。コンパイラがそれが呼ばれることを義務付けていない「些細な」コンストラクタのようなものがあります。それは継承という意味に最も近いと思いますが、上記の3つの理由から、コンストラクタを通常のメソッドと比較することは実際には役に立たないと思います。:)


1

デフォルトクラスであっても、すべてのクラスにはコンストラクタが必要です。
C ++は、特別なコンストラクタを作成する場合を除き、デフォルトのコンストラクタを作成します。
基本クラスが特殊なコンストラクターを使用する場合、派生クラスに特殊なコンストラクターを記述する必要があります(両方が同じで、それらをチェーンバックする場合でも)。
C ++ 11では、次を使用して、コンストラクターでのコードの重複を回避できます。

Class A : public B {
using B:B;
....
  1. 継承コンストラクタのセットは、で構成されます

    • 基本クラスのすべての非テンプレートコンストラクター(省略記号パラメーターがある場合は省略した後)(C ++ 14以降)
    • 既定の引数または省略記号パラメーターを持つ各コンストラクターについて、省略記号を削除し、引数リストの末尾から既定の引数を省略することによって形成されるすべてのコンストラクターシグネチャを1つずつリストします
    • 基本クラスのすべてのコンストラクターテンプレート(省略記号パラメーターがある場合は省略した後)(C ++ 14以降)
    • デフォルト引数または省略記号を持つ各コンストラクターテンプレートについて、省略記号を削除し、引数の末尾からデフォルト引数を省略することによって形成されるすべてのコンストラクターシグネチャは、1つずつリストします。
  2. 既定のコンストラクターまたはコピー/移動コンストラクターではなく、派生クラスのユーザー定義コンストラクターと署名が一致しない継承されたすべてのコンストラクターは、派生クラスで暗黙的に宣言されます。デフォルトのパラメーターは継承されません


0

次を使用できます。

MyClass() : Base()

なぜこれをしなければならないの尋ねていますか?

サブクラスには、コンストラクターで初期化する必要がある追加のプロパティがある場合があります。または、基本クラス変数を別の方法で初期化する場合があります。

サブタイプオブジェクトを他にどのように作成しますか?


ありがとう。実際、他の親クラスのメソッドのように、コンストラクターが子クラスに継承されない理由を理解しようとしています。
スワンナパッタイル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.