一、問題の記述

継承体系において、派生クラスが基底クラスのコンストラクタを使用したい場合、コンストラクタ内で明示的に宣言する必要がある。

以下のようになる:

struct A { A(int i){} }; struct B:A { B(int i):A(i){} };

ここで、BはAから派生しており、Bは

またコンストラクタ内でAのコンストラクタを呼び出し、これによってコンストラクタの受け渡しを完了する。

また、例えば以下のように、Bにメンバ変数が存在する場合:

struct A { A(int i){} }; struct B:A { B(int i):A(i),d(i){} int d; };

ここでAから派生した構造体Bは1つのメンバ変数を含み、基底クラスAを初期化すると同時にメンバdも初期化する。現在の問題は:仮に基底クラスが多数の異なるバージョンのコンストラクタを持っている場合、派生クラスでは上記の考え方に従って、対応する多くの「透過転送」コンストラクタを書かなければならない。例えば以下の通り:

struct A { A(int i) {} A(double d,int i){} A(float f,int i,const char* c){} //...など一連のコンストラクタのバージョン }; struct B:A { B(int i):A(i){} B(double d,int i):A(d,i){} B(folat f,int i,const char* c):A(f,i,e){} //......など、基底クラスのコンストラクタに対応する多数のコンストラクタ };

明らかに基底クラスのコンストラクタが多くなると、派生クラスのコンストラクタの書き方は非常に煩雑になり、かなり不便になる。

二、問題の解決

using宣言によってこの問題を簡素化することができる。1つの例を見てみよう

struct Base { void f(double i){ cout<<"Base:"<<i<<endl; } }; struct Drived:Base { using Base::f; void f(int i){ cout<<"Drived:"<<i<<endl; } };

コードでは基底クラスと派生クラスの両方が同名の関数fを宣言している。しかし派生クラスの関数は基底クラスのバージョンとは異なる。ここではusing宣言を使用して、派生クラスでも基底クラスのバージョンの関数fを使用することを示している。これにより、派生クラスにはf関数の2つのバージョンが存在することになる。ここで説明しておく必要があるのは、using宣言を使用して親クラスの同名関数を継承しない場合、派生クラスで定義されたf関数は親クラスのf関数を隠蔽する。もちろん、派生クラスがそもそもこのf同名関数を定義していなければ、基底クラスのf関数が選択される。

この方法は、コンストラクタの継承にも同様に適用できる。すなわち、派生クラスはusing文を使用して、子クラスで基底クラスのすべてのコンストラクタを継承することを宣言できる。例えば以下の通り:

struct A { A(int i) {} A(double d,int i){} A(float f,int i,const char* c){} //...など一連のコンストラクタのバージョン }; struct B:A { using A::A; //基底クラスの各コンストラクタの継承は一文で完了する //...... };

これで、using A::Aの宣言によって、基底クラスのコンストラクタをすべて派生クラスに継承できる。さらに巧妙なのは、これが暗黙的に継承を宣言することである。つまり、継承コンストラクタが関連するコードで使用されない場合、コンパイラはそれに対して実際の関数コードを生成しない。これにより、基底クラスの各種コンストラクタを透過転送するよりもターゲットコードの領域をより節約できる。しかし、ここで別の問題がある:

using文を使用して基底クラスのコンストラクタを継承する場合、派生クラスはクラス自身で定義した新しいクラスメンバを初期化できない。クラスメンバの初期化式を使用して、派生クラスのメンバにデフォルトの初期値を設定することができる。例えば:

struct A { A(int i) {} A(double d,int i){} A(float f,int i,const char* c){} //...など一連のコンストラクタのバージョン }; struct B:A { using A::A; int d{0}; };

注意:

1.継承コンストラクタにとって、引数のデフォルト値は継承されない。また、デフォルト値によって基底クラスに複数のコンストラクタのバージョンが生成される(つまり、引数が後ろから前へ順に減っていき、引数なしコンストラクタまでを含む。もちろんデフォルトのコピーコンストラクタも含まれる)。これらの関数のバージョンはすべて派生クラスに継承される。

2.継承コンストラクタにおける衝突処理:派生クラスが複数の基底クラスを持つ場合、複数の基底クラスの一部のコンストラクタにより、派生クラス内の継承コンストラクタの関数名が

引数もすべて同じになる可能性がある。その場合、継承クラス内の継承コンストラクタは不正な派生クラスコードを引き起こす。例えば:

struct A { A(int){} }; struct B { B(int){} }; struct C:A,B { using A::A; using B::B; };

ここでは派生クラスの継承コンストラクタに衝突が発生する。1つの解決方法は、継承クラス内の衝突するコンストラクタを明示的に定義し、対応する継承コンストラクタが暗黙的に生成されるのを防いで、衝突を回避することである。

struct C:A,B { using A::A; using B::B; C(int){} };

3.基底クラスのコンストラクタがプライベートコンストラクタとして宣言されている場合、または派生クラスが基底クラスを仮想継承している場合、派生クラスで継承コンストラクタを宣言することはできない。

4.継承コンストラクタを使用すると、コンパイラは派生クラスのデフォルトコンストラクタを生成しない。そのため、継承コンストラクタの引数なしバージョンが必要かどうかに注意する必要がある。

出典:http://www.cnblogs.com/yangykaifa/p/6737354.html