web-dev-qa-db-ja.com

基本クラスのポインタのみを使用して派生エンティティをコピーする(徹底的なテストなしで!)-C ++

多数の派生クラスに継承される基本クラスと、各エンティティへの基本クラスポインタを介してこれらを管理する必要があるプログラム構造があるとします。基本クラスのポインタのみがわかっている場合に、派生オブジェクト全体をコピーする簡単な方法はありますか?

周りを見回すと、_dynamic_cast_呼び出しを使用して、ベースポインターを適切な派生クラスとしてキャストできるかどうかを確認し、派生クラスのコピーコンストラクターを使用してコピーすることが可能です(非常に面倒な場合)。ただし、dynamic_castを過度に使用していることもあり、これは実際には最適なソリューションではありません。また、維持および拡張するのに全体的な頭痛の種が発生します。

私が遭遇したもう1つのよりエレガントな解決策は次のとおりです。

_class Base
{
public:
   Base(const Base& other);
   virtual Base* getCopy();
   ...
}

class Derived :public Base
{
   Derived(const Derived& other);
   virtual Base* getCopy();
   ...
}

Base* Base::getCopy()
{
   return new Base(*this));
}

Base* Derived::getCopy()
{
   return static_cast<Base*>(new Derived(*this));
}
_

次に、派生オブジェクトへの基本クラスポインタでgetCopy()を呼び出すと、基本クラスポインタが返されますが、派生オブジェクト全体がコピーされます。このメソッドは、すべての派生クラスに同様のgetCopy()関数が必要であり、すべての可能な派生オブジェクトに対してテストする必要がないため、はるかに保守しやすいと感じます。

本質的に、これは賢明ですか?それとも、これを行うためのより良い、さらにきちんとした方法がありますか?

39
geoff3jones

このアプローチは、コンパイル時に決定しようとするのではなく、任意のタイプのオブジェクトをそのオブジェクトにコピーする方法を決定する責任を軽減するため、ポリモーフィックオブジェクトをコピーするための好ましい方法です。より一般的には、コンパイル時に基本クラスのポインターが何を指しているのかわからない場合、正しいコピーを取得するために実行する必要のある多くの潜在的なコードのどれを知ることができない可能性があります。このため、実用的なソリューションではコードを動的に選択する必要があり、仮想関数はこれを行うための優れた方法です。

実際のコードに関する2つのコメント。まず、C++継承により、基本クラスのメンバー関数をオーバーライドする派生クラスで、派生関数が基本クラスのバージョンよりも具体的な型のポインターを返すことができます。これは共分散と呼ばれます。例として、基本クラス関数が

virtual Base* clone() const;

次に、派生クラスはそれを次のようにオーバーライドできます

virtual Derived* clone() const;

そして、これは完全にうまく機能します。これにより、たとえば、次のようなコードを作成できます。

Derived* d = // something...
Derived* copy = d->clone();

共変量の過負荷がなければ、これは合法ではありません。

別の詳細-あなたが持っているコードでは、明示的にstatic_castコード内のベースポインタへの派生ポインタ。これは完全に合法ですが、必須ではありません。 C++は、キャストなしで派生クラスポインタを基本クラスポインタに暗黙的に変換します。ただし、共変の戻り値の型のアイデアを使用する場合、戻り値の型は作成するオブジェクトの型と一致するため、これは表示されません。

41
templatetypedef

そこにはstatic_castは必要ないことに注意してください。 Derived *は暗黙的にBase *に変換されます。 Ken Wayneが示唆しているように、そのためにdynamic_castを使用することは絶対に避けてください。具体的な型はコンパイル時に既知であり、コンパイラーはキャストが許可されていないかどうかを通知します。

アプローチに関しては、このパターンはC#とJava as ICloneableとObject.clone()としてそれぞれ組み込まれるのに十分な標準です。

編集:

...または、これを行うためのより良い、さらにきちんとした方法はありますか?

「自己パラメーター化された基本クラス」を使用すると、毎回clone()関数を実装する手間が省けます。コピーコンストラクタを実装する必要があります。

#include <iostream>

struct CloneableBase {
    virtual CloneableBase* clone() const = 0;
};


template<class Derived>
struct Cloneable : CloneableBase {
    virtual CloneableBase* clone() const {
       return new Derived(static_cast<const Derived&>(*this));
    }
};


struct D1 : Cloneable<D1> {
    D1() {}
    D1(const D1& other) {
        std::cout << "Copy constructing D1\n";
    }
};

struct D2 : Cloneable<D2> {
    D2() {}
    D2(const D2& other) {
        std::cout << "Copy constructing D2\n";
    }
};


int main() {
    CloneableBase* a = new D1();
    CloneableBase* b = a->clone();
    CloneableBase* c = new D2();
    CloneableBase* d = c->clone();
}
3
Martin Stone
template <class T>
Base* CopyDerived(const T& other) {
  T* derivedptr = new T(other);
  Base* baseptr = dynamic_cast<Base*>(derivedptr);
  if(baseptr != NULL)
    return baseptr;
  delete derivedptr;
  // If this is reached, it is because T is not derived from Base
  // The exception is just an example, handle in whatever way works best
  throw "Invalid types in call to Copy";
}

これには、コピーする各派生クラスで公的にアクセス可能なコピーコンストラクターのみが必要です。

0
Jon Garbe

ええ、あなたのアイデアは行く方法です。また、派生クラスは、ディープコピーを実行するかシャローコピーを実行するかを選択できます。

将来の参考のために、1つの(やや気難しい)ポイントがあります。安全性の観点から、ポリモーフィック変換ではstatic_castよりもdynamic_castを使用することをお勧めします。それは私の注意を引くものの1つにすぎません。