克隆指针C ++

时间:2012-07-25 17:26:32

标签: c++ pointers polymorphism clone

这个问题与复制和指针多态有关。请考虑以下代码。我们有两个类:基础派生,它们只是常规对象。然后我们有了 Foo 类,它有一个指向 Base 的指针作为其唯一成员。

main函数中描述了 Foo 的典型用法。 Foo::SetMemberX的输入可能是也可能不是临时对象。

问题在于我希望Foo::SetMember创建传递对象的正确副本,并将其地址指定为Base*Foo::mMember

我已经设法提出了4种可能的解决方案,但对我来说似乎都不是很优雅。前三项显示在Foo::SetMember1Foo::SetMember2Foo::SetMember3下面的代码中。第四个选项是将内存分配留给用户(例如foo.SetMember(new Derived())),这对于明显的内存安全问题不是很理想。 Foo 应该负责内存管理,而不是用户。

#include <iostream>

template <typename tBase, typename tPointer>
void ClonePointer(tBase*& destination, const tPointer* pointer)
{
    destination = static_cast<tBase*>(new tPointer(*pointer));
}

// Base can be a virtual class
class Base
{
public:

    virtual void Function()
    {
        std::cout << "Base::Function()" << std::endl;
    }

    virtual Base* Clone() const = 0;
};

class Derived : public Base
{
public:

    virtual void Function()
    {
        std::cout << "Derived::Function()" << std::endl;
    }

    virtual Base* Clone() const
    {
        return new Derived(*this);
    }
};

class Foo
{
public:

    Foo() : mMember(NULL) { }

    ~Foo()
    {
        if (mMember != NULL)
        {
            delete mMember;
            mMember = NULL;
        }
    }

    template <typename T>
    void SetMember1(const T& t)
    {
        if (mMember != NULL)
        {
            delete mMember;
            mMember = NULL;
        }

        ClonePointer(mMember, &t);
    }

    void SetMember2(const Base& b)
    {
        mMember = b.Clone();
    }

    template <typename T>
    void SetMember3(const T& t)
    {
        if (mMember != NULL)
        {
            delete mMember;
            mMember = NULL;
        }

        mMember = new T(t);
    }

    Base& GetMember()
    {
        return *mMember;
    }

private:

    Base* mMember;
};

int main(int argc, char** argv)
{
    {
        Foo f1;
        Foo f2;
        Foo f3;

        // The input may or may not be a tempoary/RValue reference

        f1.SetMember1(Derived());
        f2.SetMember2(Derived());
        f3.SetMember3(Derived());

        f1.GetMember().Function();
        f2.GetMember().Function();
        f3.GetMember().Function();
    }

    // Output:
    // Derived::Function();
    // Derived::Function();
    // Derived::Function();

    system("pause"); // for quick testing
}

第一种方法(Foo::SetMember1)的问题在于我有一个随机的,免费的模板函数和一个模板访问器(请参阅下面第三种方法的问题)。

第二种方法(Foo::SetMember2)的问题是每个派生类都必须实现自己的Clone函数。这对于类用户来说是太多的样板代码,因为会有很多来自 Base 的类。如果我可以以某种方式自动执行此操作,或者使用实现的模板Clone函数创建基本可克隆类(没有每个 Base - 派生类必须显式调用它) ,这将是理想的解决方案。

第三种方法(Foo::SetMember3)的问题在于我需要 Foo 的模板访问器。这可能并不总是可行,尤其是因为非模板类​​中不允许使用虚拟模板方法( Foo 不能是模板本身),这是可能需要的功能。

我的问题是:

  1. 这些是我唯一的选择吗?

  2. 我缺少一个更好,更优雅的解决方案吗?

  3. 有没有办法创建一个基本可克隆类并从中派生 Base ,并且DerivedType::Clone()会自动进行克隆?

2 个答案:

答案 0 :(得分:3)

这是一个或多或少强大的Clonable类,可以继承到任何深度。它使用CRTP和Alecsandrescu样式的交织继承模式。

#include <iostream>

// set up a little named template parameters rig
template <class X> struct Parent{};
template <class X> struct Self{};
template<class A, class B> struct ParentChild;

// can use ...< Parent<X>, Self<Y> >...
template<class A, class B> struct ParentChild< Parent<A>, Self<B> >
{
    typedef A parent_type;
    typedef B child_type;
};

// or ...< Self<Y>, Parent<X> >
template<class A, class B> struct ParentChild< Self<B>, Parent<A> >
{
    typedef A parent_type;
    typedef B child_type;
};

// nothing, really
struct Nada
{
    // except the virtual dtor! Everything clonable will inherit from here.
    virtual ~Nada() {}
};

// The Clonable template. Accepts two parameters: 
// the child class (as in CRTP), and the parent class (one to inherit from)
// In any order.
template <class A, class B = Parent<Nada> > class Clonable : 
    public ParentChild<A,B>::parent_type
{
  public:
    // a nice name to refer to in the child class, instead of Clonable<A,B>
    typedef Clonable Parent;

    // this is our child class
    typedef typename ParentChild<A,B>::child_type child_type;

    // This is the clone() function returning the cloned object
    // Non-virtual, because the compiler has trouble with covariant return
    // type here. We have to implemens something similar, by having non-virtual
    // that returns the covariant type calling virtual that returns the 
    // base type, and some cast.
    child_type* clone()
    {
        return static_cast<child_type*>(private_clone());
    }

    // forward some constructor, C++11 style
    template<typename... Args> Clonable(Args&&... args): 
        ParentChild<A,B>::parent_type(args...) {}

  private:

    // this is the main virtual clone function
    // allocates the new child_type object and copies itself
    // with the copy constructor
    virtual Nada* private_clone() 
    {
        // we *know* we're the child_type object
        child_type* me = static_cast<child_type*>(this);
        return new child_type(*me);
    };
};

// Test drive and usage example

class Foo : public Clonable < Self<Foo> >
{
  public: 
    Foo (int) { std::cout << "Foo::Foo(int)\n"; }
    Foo (double, char) { std::cout << "Foo::Foo(double, char)\n"; }
    Foo (const Foo&) { std::cout << "Foo::Foo(Foo&)\n"; }
};

class Bar : public Clonable < Self<Bar>, Parent<Foo> >
{
  public:
    // cannot say Bar (int i) : Foo(i), unfortunately, because Foo is not
    // our immediate parent
    // have to use the Parent alias 
    Bar (int i) : Parent(i) 
        { std::cout << "Bar::Bar(int)\n"; }
    Bar (double a, char b) : Parent(a, b) 
        { std::cout << "Bar::Bar(double, char)\n"; }
    Bar (const Bar& b) : Parent(b) 
        { std::cout << "Bar::Bar(Bar&)\n"; }

    ~Bar() { std::cout << "Bar::~Bar()\n"; }
};

int main ()
{
    Foo* foo1 = new Bar (123);
    Foo* foo2 = foo1->clone(); // this is really a Bar
    delete foo1;
    delete foo2;
}

答案 1 :(得分:2)

对于第二种方法,您可以使用CRTP,并且不需要在每个派生类中编写克隆方法:

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

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

struct Derived: CloneableBase<Derived> {};