2009-08-04 11 views
73

私は、コンストラクタのセットと代入演算子でクラスBを持っています。C++で基本クラスのコンストラクタと代入演算子を使用するには?

class B 
{ 
public: 
    B(); 
    B(const string & s); 
    B(const B & b){(*this) = b;}; 
    B & operator= (const B & b); 
private: 
    virtual void foo(); 
    // and other private member variables and functions 
} 

私は、関数fooを上書きします()、および他の変更が必要とされない継承クラスDを作成したいです。

しかし、私はDはBとしてコピーコンストラクタと代入演算子を含むコンストラクタの同じセットを、持っていると思います:

D(const D & d){(*this) = d;}; 
    D & operator= (const D & d); 

が、私はDでそれらのすべてを書き換える必要がありますか、または使用する方法がありますBのコンストラクタと演算子? Bのプライベートメンバー変数のすべてにアクセスする必要があるため、特に代入演算子を書き換えないようにしたいと考えています。

+0

'foo'メソッドをオーバーライドしたいのであれば、' B :: operator =; 'を使って代入演算子を継承することができますが、コピーと移動のコンストラクタは継承できません:https://stackoverflow.com/q/49045026/5447906 –

答えて

93

明示的にコンストラクタと代入演算子を呼び出すことができます。

class Base { 
//... 
public: 
    Base(const Base&) { /*...*/ } 
    Base& operator=(const Base&) { /*...*/ } 
}; 

class Derived : public Base 
{ 
    int additional_; 
public: 
    Derived(const Derived& d) 
     : Base(d) // dispatch to base copy constructor 
     , additional_(d.additional_) 
    { 
    } 

    Derived& operator=(const Derived& d) 
    { 
     Base::operator=(d); 
     additional_ = d.additional_; 
     return *this; 
    } 
}; 

興味深いのは、これはあなたが明示的に(それはその後、コンパイラが生成機能を使用しています)これらの関数を定義していない場合でも動作することです。

class ImplicitBase { 
    int value_; 
    // No operator=() defined 
}; 

class Derived : public ImplicitBase { 
    const char* name_; 
public: 
    Derived& operator=(const Derived& d) 
    { 
     ImplicitBase::operator=(d); // Call compiler generated operator= 
     name_ = strdup(d.name_); 
     return *this; 
    } 
}; 
+0

これはどういう意味ですか? 'Base(const Base)' – qed

+1

@CravingSpiritそれは[コピーコンストラクタ](http://en.wikipedia.org/wiki/Copy_constructor)です(引数名は省略されています)。 – Motti

+0

ありがとうございます。なぜ、operator = overloadingが既に存在する場合、コピーコンストラクタが必要なのでしょうか? – qed

16

短い答え:あなたの派生クラスのD 'は、デフォルトのバージョンは(コンパイラによって生成された新しいメンバ変数をすべき含まれていない場合は

:はいあなたはD

長い答えで作業を繰り返す必要がありますうまく動作します)。デフォルトのコピーコンストラクタは親コピーコンストラクタを呼び出し、デフォルトの代入演算子は親代入演算子を呼び出します。

しかし、クラス 'D'にリソースが含まれている場合は、いくつかの作業を行う必要があります。

私はあなたのコピーコンストラクタは少し奇妙見つける:彼らはベースアップからコピー構築されるように

B(const B& b){(*this) = b;} 

D(const D& d){(*this) = d;} 

は、通常、コンストラクタチェーンをコピーします。ここでは、代入演算子を呼び出すので、コピーコンストラクタはデフォルトのコンストラクタを呼び出して、オブジェクトをボトムアップから初期化する必要があります。次に、代入演算子を使用して再び下に移動します。これはむしろ非効率なようです。

割り当てを行うと、下から上(上から下へ)のコピーが行われますが、それを実行して強力な例外保証を提供することは難しいようです。ある時点でリソースがコピーに失敗し、例外をスローすると、オブジェクトは不確定な状態になります(これは悪いことです)。

通常、私はそれが他の方法で行われているのを見ました。
代入演算子は、コピーコンストラクタとスワップの観点から定義されています。これは、強力な例外保証を提供しやすくするためです。私はあなたがこのようにすることで強い保証を提供できるとは思わない(私は間違っている可能性がある)。

class X 
{ 
    // If your class has no resources then use the default version. 
    // Dynamically allocated memory is a resource. 
    // If any members have a constructor that throws then you will need to 
    // write your owen version of these to make it exception safe. 


    X(X const& copy) 
     // Do most of the work here in the initializer list 
    { /* Do some Work Here */} 

    X& operator=(X const& copy) 
    { 
     X tmp(copy);  // All resource all allocation happens here. 
          // If this fails the copy will throw an exception 
          // and 'this' object is unaffected by the exception. 
     swap(tmp); 
     return *this; 
    } 
    // swap is usually trivial to implement 
    // and you should easily be able to provide the no-throw guarantee. 
    void swap(X& s) throws() 
    { 
     /* Swap all members */ 
    } 
}; 

XからクラスDを派生させても、このパターンには影響しません。
確かに、基本クラスへの明示的な呼び出しを行うことによって少しの作業を繰り返す必要がありますが、これは比較的簡単です。

class D: public X 
{ 

    // Note: 
    // If D contains no members and only a new version of foo() 
    // Then the default version of these will work fine. 

    D(D const& copy) 
     :X(copy) // Chain X's copy constructor 
     // Do most of D's work here in the initializer list 
    { /* More here */} 



    D& operator=(D const& copy) 
    { 
     D tmp(copy);  // All resource all allocation happens here. 
          // If this fails the copy will throw an exception 
          // and 'this' object is unaffected by the exception. 
     swap(tmp); 
     return *this; 
    } 
    // swap is usually trivial to implement 
    // and you should easily be able to provide the no-throw guarantee. 
    void swap(D& s) throws() 
    { 
     X::swap(s); // swap the base class members 
     /* Swap all D members */ 
    } 
}; 
+0

+1。あなたがそれにいるので、あなたのスワップメンバメソッドに委譲するあなたのタイプのstd :: swapに特殊化を追加してください: 'namespace std {template <> void std :: swap(D&lhs、D&rhs){lhs.swap (rhs); }} 'このようにして、特殊なスワップ操作をSTLアルゴリズムで使用できます。 –

+1

X *と同じ名前空間にフリースワップ関数を追加すると、(ADLを介して)同じ効果があるはずですが、MSVCが間違ってstd :: swapを明示的に呼び出しているので、dribeasを正しく作成する... –

+0

標準名前空間にものを追加することは許可されていません。 –

4

あなたは最も可能性の高いデザインの欠陥(ヒント:をスライスし、エンティティのセマンティクス対値のセマンティクス)を持っています。多形階層からのオブジェクト上に完全なコピー/ 値セマンティクスを持つことは、しばしば必要ではありません。あなたが後でそれを必要とするかもしれない場合に備えて、それを提供したいなら、あなたはそれが決して必要でないことを意味します。代わりに(例えばboost :: noncopyableから継承して)基本クラスをコピー不可能にしてください。それだけです。

のみ正解時に、そのような必要性本当にが表示されますが封筒文字のイディオム、または通常のオブジェクトショーン親とアレクサンダー・ステパノフIIRCによっての記事から少しのフレームワークです。他のすべてのソリューションはスライスやLSPに問題を与えます。

1

デフォルトではまたはのコンストラクタでないすべてのコンストラクタを再定義する必要があります。 SBIが述べたように、あなたが任意のコンストラクタを定義する場合、こと

struct base 
{ 
    base() { std::cout << "base()" << std::endl; } 
    base(base const &) { std::cout << "base(base const &)" << std::endl; } 
    base& operator=(base const &) { std::cout << "base::=" << std::endl; } 
}; 
struct derived : public base 
{ 
    // compiler will generate: 
    // derived() : base() {} 
    // derived(derived const & d) : base(d) {} 
    // derived& operator=(derived const & rhs) { 
    // base::operator=(rhs); 
    // return *this; 
    // } 
}; 
int main() 
{ 
    derived d1;  // will printout base() 
    derived d2 = d1; // will printout base(base const &) 
    d2 = d1;   // will printout base::= 
} 

注:あなたは、すべてのベースのバージョンを呼び出します(標準に従って)コンパイラによって提供されるようなコピーコンストラクタや代入演算子を再定義する必要はありません。コンパイラはあなたのためにデフォルトのコンストラクタを生成せず、コピーコンストラクタを含みます。

+0

他のctor(これにはコピーctorを含む)が定義されている場合、コンパイラはデフォルトのctorを提供しないことに注意してください。したがって、 'derived'にデフォルトのctorを持たせたい場合は、明示的に定義する必要があります。 – sbi

1

元のコードが間違っている:

一般的に
class B 
{ 
public: 
    B(const B& b){(*this) = b;} // copy constructor in function of the copy assignment 
    B& operator= (const B& b); // copy assignment 
private: 
// private member variables and functions 
}; 

コピー代入は、資源やコピーコンストラクタドンを解放しなければなりませんので、あなたは、コピー代入の面でコピーコンストラクタを定義することはできません't !!!

検討し、これを理解するには、次の

class B 
{ 
public: 
    B(Other& ot) : ot_p(new Other(ot)) {} 
    B(const B& b) {ot_p = new Other(*b.ot_p);} 
    B& operator= (const B& b); 
private: 
    Other* ot_p; 
}; 

をメモリリークを回避するために、コピー代入が最初ot_pが指すメモリ削除する必要があります。だから、

B::B& operator= (const B& b) 
{ 
    delete(ot_p); // <-- This line is the difference between copy constructor and assignment. 
    ot_p = new Other(*b.ot_p); 
} 
void f(Other& ot, B& b) 
{ 
    B b1(ot); // Here b1 is constructed requesting memory with new 
    b1 = b; // The internal memory used in b1.op_t MUST be deleted first !!! 
} 

を、コピーコンストラクタと代入をコピーするには、異なっています前者の構造とオブジェクトは初期化されたメモリに入り、後で新しいオブジェクトを構築する前に既存のメモリを最初に解放しなければならないからです。

最初にこの記事で提案されたものを行う場合:

B(const B& b){(*this) = b;} // copy constructor 

あなたがunexistingメモリを消去されます。

関連する問題