C ++虚拟构造函数,没有clone()

时间:2010-05-04 14:04:48

标签: c++ prototype clone virtual-copy

我想执行多态类指针的STL容器的“深层复制”。

我了解 Prototype 设计模式,通过 Virtual Ctor Idiom 实现,如C++ FAQ Lite, Item 20.8中所述。
它简单明了:

struct ABC // Abstract Base Class
{
    virtual ~ABC() {}
    virtual ABC * clone() = 0;
};
struct D1 : public ABC
{
    virtual D1 * clone() { return new D1( *this ); } // Covariant Return Type
};

然后是深层复制:

for( i = 0; i < oldVector.size(); ++i )
    newVector.push_back( oldVector[i]->clone() );

缺点

作为Andrei Alexandrescu states it

  

clone()实现必须在所有派生类中遵循相同的模式;尽管它具有重复的结构,但没有合理的方法来自动定义clone()成员函数(超出宏,即)。

此外,ABC的客户可能会做坏事。 (我的意思是,没有什么能阻止客户做坏事,所以 会发生。)

更好的设计?

我的问题是:是否有另一种方法可以使抽象基类克隆而不需要派生类来编写与克隆相关的代码? (助手类?模板?)


以下是我的背景。希望它能帮助理解我的问题。

我正在设计一个类层次结构来对类Image执行操作:

struct ImgOp
{
    virtual ~ImgOp() {}
    bool run( Image & ) = 0;
};

图像操作是用户定义的:类层次结构的客户端将实现从ImgOp派生自己的类:

struct CheckImageSize : public ImgOp
{
    std::size_t w, h;
    bool run( Image &i ) { return w==i.width() && h==i.height(); }
};
struct CheckImageResolution { ... };
struct RotateImage          { ... };
...

可以在图像上按顺序执行多个操作:

bool do_operations( vector< ImgOp* > v, Image &i )
{
    for_each( v.begin(), v.end(),
        /* bind2nd( mem_fun( &ImgOp::run ), i ... ) don't remember syntax */ );
}

如果有多个图像,则可以在多个线程上拆分和共享该集。为了确保“线程安全”,每个线程必须拥有v中包含的自己的所有操作对象的副本 - v成为在每个线程中深度复制的原型

已编辑:线程安全版本使用Prototype设计模式强制执行指向对象的副本 - 而不是ptrs:

struct ImgOp
{
    virtual ~ImgOp() {}
    bool run( Image & ) = 0;
    virtual ImgOp * clone() = 0; // virtual ctor
};

struct CheckImageSize : public ImgOp       { /* no clone code */ };
struct CheckImageResolution : public ImgOp { /* no clone code */ };
struct RotateImage : public ImgOp          { /* no clone code */ };

bool do_operations( vector< ImgOp* > v, Image &i )
{
    // In another thread
    vector< ImgOp* > v2;
    transform( v.begin(), v.end(),                       // Copy pointed-to-
        back_inserter( v2 ), mem_fun( &ImgOp::clone ) ); // objects
    for_each( v.begin(), v.end(),
        /* bind2nd( mem_fun( &ImgOp::run ), i ... ) don't remember syntax */ );
}

当图像操作类很小时,这是有意义的:不要序列化对ImgOp的唯一实例的访问,而是为每个线程提供自己的副本。

困难的部分是避免新ImgOp派生类的编写者编写任何与克隆相关的代码。 (因为这是实施细节 - 这就是为什么我用好奇的重复模式驳回保罗的答案。)

3 个答案:

答案 0 :(得分:7)

您可以使用奇怪的递归模式,但这可能会使您的代码不易读取。 您仍然需要复制构造函数。它的工作原理如下。

struct ABC // Abstract Base Class
{
    virtual ~ABC() {}
    virtual ABC * clone() const = 0;
};



template <class TCopyableClass>
struct ClonableABC : public ABC
{
    virtual ABC* clone() const {
       return new TCopyableClass( *(TCopyableClass*)this );
    } 
};


struct SomeABCImpl : public ClonableABC<SomeABCImpl>
{};

答案 1 :(得分:2)

仅供参考,这是我出来的设计。感谢Paul和FredOverflow的投入。 (还有Martin York的评论。)

步骤#1,编译时多态与模板

使用模板和隐式接口在编译时执行多态性:

template< typename T >
class ImgOp
{
    T m_t; // Not a ptr: when ImgOp is copied, copy ctor and
           // assignement operator perform a *real* copy of object
    ImageOp ( const ImageOp &other ) : m_t( other .m_t ) {}
    ImageOp & operator=( const ImageOp & );
public:
    ImageOp ( const T &p_t ) : m_t( p_t ) {}
    ImageOp<T> * clone() const { return new ImageOp<T>( *this ); }
    bool run( Image &i ) const { return m_t.run( i); }
};

// Image operations need not to derive from a base class: they must provide
// a compatible interface
class CheckImageSize       { bool run( Image &i ) const {...} };
class CheckImageResolution { bool run( Image &i ) const {...} };
class RotateImage          { bool run( Image &i ) const {...} };

现在所有与克隆相关的代码都位于一个唯一的类中。但是,现在不可能让ImgOp的容器在不同的操作上模板化:

vector< ImgOp > v;           // Compile error, ImgOp is not a type
vector< ImgOp< ImgOp1 > > v; // Only one type of operation :/

步骤#2,添加抽象级别

添加充当接口的非模板库:

class AbstractImgOp
{
    ImageOp<T> * clone() const = 0;
    bool run( Image &i ) const = 0;
};

template< typename T >
class ImgOp : public AbstractImgOp
{
    // No modification, especially on the clone() method thanks to
    // the Covariant Return Type mechanism
};

现在我们可以写:

vector< AbstractImgOp* > v;

但是操作图像操作对象变得很困难:

AbstractImgOp *op1 = new AbstractImgOp;
    op1->w = ...; // Compile error, AbstractImgOp does not have
    op2->h = ...; // member named 'w' or 'h'

CheckImageSize *op1 = new CheckImageSize;
    op1->w = ...; // Fine
    op1->h = ...;
AbstractImgOp *op1Ptr = op1; // Compile error, CheckImageSize does not derive
                             // from AbstractImgOp? Confusing

CheckImageSize op1;
    op1.w = ...; // Fine
    op1.h = ...;
CheckImageResolution op2;
    // ...
v.push_back( new ImgOp< CheckImageSize >( op1 ) );       // Confusing!
v.push_back( new ImgOp< CheckImageResolution >( op2 ) ); // Argh

步骤#3,添加“克隆指针”类

根据FredOverflow的解决方案,制作一个克隆指针,使框架更易于使用 但是,这个指针不需要被模板化,因为它只能保存一种类型的ptr - 只需要对ctor进行模板化:

class ImgOpCloner
{
    AbstractImgOp *ptr; // Ptr is mandatory to achieve polymorphic behavior
    ImgOpCloner & operator=( const ImgOpCloner & );
public:
    template< typename T >
    ImgOpCloner( const T &t ) : ptr( new ImgOp< T >( t ) ) {}
    ImgOpCloner( const AbstractImgOp &other ) : ptr( other.ptr->clone() ) {}
    ~ImgOpCloner() { delete ptr; }
    AbstractImgOp * operator->() { return ptr; }
    AbstractImgOp & operator*() { return *ptr; }
};

现在我们可以写:

CheckImageSize op1;
    op1.w = ...; // Fine
    op1.h = ...;
CheckImageResolution op2;
    // ...
vector< ImgOpCloner > v;
v.push_back( ImgOpCloner( op1 ) ); // This looks like a smart-ptr, this is not
v.push_back( ImgOpCloner( op2 ) ); // confusing anymore -- and intent is clear

答案 2 :(得分:1)

  

然后是深拷贝:[for loop]

您使客户端显式克隆向量。我不确定这是否能回答你的问题,但我建议使用一个智能指针矢量,这样克隆就会自动发生。

std::vector<cloning_pointer<Base> > vec;
vec.push_back(cloning_pointer<Base>(new Derived()));

// objects are automatically cloned:
std::vector<cloning_pointer<Base> > vec2 = vec;

当然,在调整矢量或其他内容时,您不希望发生这些隐式副本,因此您需要能够区分副本和移动。这是cloning_pointer的C ++ 0x玩具实现,您可能需要根据自己的需要进行调整。

#include <algorithm>

template<class T>
class cloning_pointer
{
    T* p;

public:

    explicit cloning_pointer(T* p)
    {
        this->p = p;
    }

    ~cloning_pointer()
    {
        delete p;
    }

    cloning_pointer(const cloning_pointer& that)
    {
        p = that->clone();
    }

    cloning_pointer(cloning_pointer&& that)
    {
        p = that.p;
        that.p = 0;
    }

    cloning_pointer& operator=(const cloning_pointer& that)
    {
        T* q = that->clone();
        delete p;
        p = q;
        return *this;
    }

    cloning_pointer& operator=(cloning_pointer&& that)
    {
        std::swap(p, that.p);
        return *this;
    }

    T* operator->() const
    {
        return p;
    }

    T& operator*() const
    {
        return *p;
    }
};

Julien:&&不是“ref ref”,它是一个rvalue引用,只能绑定到可修改的rvalues。请参阅此优秀(但遗憾的是略显过时)tutorialvideo,了解右值参考及其工作原理。