2010-05-04 31 views
8

我想执行指向多态类的指针的STL容器的“深拷贝”。C++虚构造器,没有克隆()

我了解原型设计模式,由虚拟构造函数成语来实现,如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()); 

缺点

正如安德烈Alexandrescu的states it

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

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

更好的设计?

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


以下是我的上下文。希望这将有助于理解我的问题。

我设计一个类层次结构上的一类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成为要在每个线程中深度复制的原型。

编辑:线程安全的版本使用原型设计模式来执行的指向的对象副本 - 不师生比:

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衍生类来编写任何与克隆相关的代码。 (因为这是实现细节 - 这就是为什么我用奇怪的循环模式解雇Paul的答案的原因。)

+1

我认为实际上可以自动化clone()'(如果有点不雅):http://tydland.net/2009/06/covariant-templatized-virtual-copy-constructors/ – 2010-05-04 14:10:51

+1

@tyler McHenry :但是使其自动化会鼓励使用不当,然后C++就会开始看起来像Java。我认为几乎所有的clone()用法都是错误的(在少数情况下,需要让人们去做实际的工作),不幸的是,这种方法很容易使用,从而导致错误的设计和实现。 – 2010-05-04 15:16:54

+0

刚才定义和使用拷贝构造函数的方式有什么问题?正如@马丁所说,你绝对不希望对象被任意地克隆。 – jalf 2010-05-04 15:54:24

回答

2

仅供参考,这是我提出的设计。感谢Paul和FredOverflow的投入。 (和马丁约克的评论)

第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的模板化 - 只有构造函数需要被模板:

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 
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> 
{}; 
+0

+1:总是对一个曲折的递归模式应用感兴趣... – neuro 2010-05-04 14:34:04

+2

+1好方法。正如泰勒在评论中提到的文章指出的,你不再有协变的返回类型。我从来不明白为什么这是一个很大的交易,但。我会调用克隆方法的地方,我不知道派生类型。 – Dan 2010-05-04 14:55:26

+0

据我所知,如果我需要子类'SomeABCImpl',这是行不通的,对吧? – doublep 2010-05-04 15:41:03

1

深副本则:for循环]

你让客户克隆载体明确。我不确定这是否回答你的问题,但我会建议一个智能指针向量,以便克隆自动发生。

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; 

当然,你不希望这些隐式副本,以调整矢量什么时候发生,所以你需要能够从移动区分副本。这是我的C++ 0x玩具实现cloning_pointer,您可能需要根据自己的需要进行调整。

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

于连:&&不是一个“REF的参考”,它是仅结合到修改的右值右值参考。看到这个出色的(但遗憾的是稍微过时)tutorialvideo了解右值引用及其工作方式的概述。

+0

这很有趣。请你可以提供关于“移动”机制的更多细节:克隆指针如何知道它必须移动而不克隆?每当它被赋予ref时,它的确意味着它正在调整大小? 顺便说一句,你不应该使用'std :: swap()',而是'使用std :: swap;交换()。 – 2010-05-05 07:55:04

+0

@Julien我知道swap的成语,但我认为用'std :: swap'交换两个原始指针是很安全的;-)我包含了两个链接,可以很好地解释移动机制。 – fredoverflow 2010-05-05 08:16:24

+0

非常感谢您的详细信息(以及交换更正:)我喜欢助手类'cloning_pointer',因为它使意图清晰。但是,它不能解决我的问题,因为派生类仍然需要实现'clone()'方法。 – 2010-05-05 09:20:11