104

B一連のコンストラクターと代入演算子を持つクラスがあります。

ここにあります:

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
};

D関数をオーバーライドするだけの継承クラスを作成したいのですがfoo()、他の変更は必要ありません。

しかし、Dコピーコンストラクターと代入演算子を含むコンストラクターの同じセットが必要ですB:

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

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

4

5 に答える 5

137

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

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;
    }
};  
于 2009-08-04T11:32:25.663 に答える
20

簡単な回答: はい、D で作業を繰り返す必要があります。

長い答え:

派生クラス 'D' に新しいメンバー変数が含まれていない場合は、既定のバージョン (コンパイラによって生成されたもの) が問題なく動作するはずです。デフォルトの Copy コンストラクターは親のコピー コンストラクターを呼び出し、デフォルトの代入演算子は親の代入演算子を呼び出します。

ただし、クラス「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 */
    }
};
于 2009-08-04T10:39:10.123 に答える
3

ほとんどの場合、設計に欠陥があります (ヒント:スライスエンティティ セマンティクス値セマンティクス)。多くの場合、ポリモーフィック階層のオブジェクトに完全なコピー/値のセマンティクスを持たせる必要はまったくありません。後で必要になる可能性がある場合に備えて提供したい場合は、それを必要としないことを意味します. 代わりに、基本クラスをコピー不可にします (たとえば、boost::noncopyable から継承することにより)。それだけです。

そのような必要性が実際に現れたときの唯一の正しい解決策は、 envelop-letter idiom 、またはSean Parent と Alexander Stepanov IIRC による通常のオブジェクトに関する記事の小さなフレームワークです。他のすべてのソリューションでは、スライスや LSP で問題が発生します。

この件については、 C++CoreReference C.67: C.67: A base class should suppresscopyも参照してください。「コピー」が必要な場合は、代わりに仮想クローンを提供してください

于 2009-08-04T10:43:36.730 に答える