web-dev-qa-db-ja.com

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

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

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

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

子クラスが(を継承する場合、どのように問題を引き起こす可能性がありますか?継承することによって、子クラスは親クラスのメソッドなどをオーバーライドできます。親クラスのメソッドにアクセスするだけではありません)親コンストラクタ?

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

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

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

34

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

クラスが構築されるとき、常に発生する必要があるいくつかの事柄があります。基本クラスのコンストラクター(存在する場合)および直接メンバーを呼び出す必要があり、仮想関数がある場合は、vpointerを正しく設定する必要があります。 。クラスのコンストラクターを指定しない場合、コンパイラーwillは、必要なアクションのみを実行するものを作成します。 doコンストラクタを提供するが、必要なアクションの一部(たとえば、一部のメンバーの初期化)を見逃した場合、コンパイラは不足しているアクションをコンストラクタに自動的に追加します。このようにして、コンパイラーは、各クラスに少なくとも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だけを受け取ります。

「親コンストラクターを継承する」という意味が明確ではありません。 Wordoverridingを使用しているため、 多相仮想関数のように動作するコンストラクタ について考えている可能性があります。 これはコードパターンの一般的な名前です で、オブジェクトの既存のインスタンスを実際に作成して別のインスタンスを作成する必要があるため、「仮想コンストラクタ」という用語は意図的に使用していません。

「仮想コンストラクター」パターン以外のポリモーフィックコンストラクターにはほとんど実用性がなく、実際のポリモーフィックコンストラクターが使用される具体的なシナリオを思い付くのは困難です。 がリモートで有効な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.
}

この場合、呼び出されるコンストラクターは、構築または割り当てられる変数の具象型によって異なります。解析/コード生成中に検出するのは複雑であり、実用性はありません。構築している具象型を知っていて、派生クラスの特定のコンストラクターを作成している必要があります。次のvalidC++コードはまったく同じで、少し短く、機能がより明確です。

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つの値のコンストラクターを継承した場合、異なる高さと幅の正方形を作成できます...それは論理的に間違っているので、そのコンストラクターを非表示にする必要があります。

7

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

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

3
Pieter B

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

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

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

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

3
Dunk

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

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

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

なぜすべてのクラスにコンストラクタが必要なのですか?これは複雑な質問であり、答えはコンパイラに依存していると思います。 「簡単な」コンストラクタのように、コンパイラが呼び出されることを要求しないものがあるようです。それは継承という意味で最も近いものだと思いますが、上記の3つの理由から、コンストラクタを通常のメソッドと比較することはあまり役に立ちません。 :)

2
Sarien

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

Class A : public B {
using B:B;
....
  1. 継承するコンストラクターのセットは、

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

1
MeduZa

以下を使用できます。

MyClass() : Base()

なぜ質問しているのですか?

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

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

0
Tom Tom