C++ 虚构造函数,没有 clone() [英] C++ Virtual Constructor, without clone()
问题描述
我想对指向多态类的指针的 STL 容器执行深度复制".
我了解 Prototype 设计模式,它是通过 Virtual Ctor Idiom 实现的,如C++ FAQ Lite,第 20.8 项.
它简单明了:
struct ABC//抽象基类{虚拟 ~ABC() {}虚拟ABC *克隆()= 0;};结构 D1:公共 ABC{virtual D1 * clone() { return new D1( *this );}//协变返回类型};
一个深拷贝是:
for( i = 0; i clone());
缺点
正如 Andrei Alexandrescu 所说:
<块引用>clone()
实现必须在所有派生类中遵循相同的模式;尽管结构重复,但没有合理的方法来自动定义 clone()
成员函数(除了宏之外).
此外,ABC
的客户端可能会做坏事.(我的意思是,没有什么能阻止客户做坏事,所以,它会发生.)
更好的设计?
我的问题是:是否有另一种方法可以使抽象基类可克隆而不需要派生类编写与克隆相关的代码?(助手类?模板?)
<小时>以下是我的背景.希望它有助于理解我的问题.
我正在设计一个类层次结构来对类 Image
执行操作:
struct ImgOp{虚拟 ~ImgOp() {}布尔运行(图像和)= 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 v, Image &i ){for_each( v.begin(), v.end(),/* bind2nd( mem_fun( &ImgOp::run ), i ... ) 不记得语法 */);}
如果有多个图像,该集合可以拆分并通过多个线程共享.为了保证线程安全",每个线程必须拥有自己的v
中所有操作对象的副本——v
成为原型在每个线程中进行深度复制.
线程安全版本使用原型设计模式来强制指向对象的复制——而不是ptrs:
struct ImgOp{虚拟 ~ImgOp() {}布尔运行(图像和)= 0;虚拟 ImgOp * clone() = 0;//虚拟构造函数};struct CheckImageSize : public ImgOp {/* 没有克隆代码 */};struct CheckImageResolution : public ImgOp {/* 没有克隆代码 */};struct RotateImage : public ImgOp {/* 没有克隆代码 */};bool do_operations( vector v, Image &i ){//在另一个线程中向量v2;transform( v.begin(), v.end(),//复制指向-back_inserter( v2 ), mem_fun( &ImgOp::clone ) );//对象for_each( v.begin(), v.end(),/* bind2nd( mem_fun( &ImgOp::run ), i ... ) 不记得语法 */);}
当图像操作类较小时,这很有意义:不要序列化对 ImgOp
的唯一实例的访问,而是为每个线程提供自己的副本.
困难的部分是避免新的 ImgOp
派生类的编写者编写任何与克隆相关的代码.(因为这是实现细节——这就是为什么我用 Curiously Recurring Pattern 驳回了 Paul 的回答.)
仅供参考,这是我提出的设计.感谢 Paul 和 FredOverflow 的投入.(还有 Martin York 的评论.)
第 1 步,带有模板的编译时多态性
在编译时使用模板和隐式接口执行多态:
模板<类型名称 T >类 ImgOp{tm_t;//不是 ptr:当 ImgOp 被复制时,复制 ctor 和//赋值运算符执行对象的*真实*副本ImageOp ( const ImageOp &other ) : m_t( other .m_t ) {}ImageOp &运算符=( const ImageOp & );上市:ImageOp ( const T &p_t ) : m_t( p_t ) {}ImageOp T* clone() const { return new ImageOp( *this );}bool run( Image &i ) const { return m_t.run( i);}};//图像操作不需要从基类派生:它们必须提供//兼容的接口class CheckImageSize { bool run( Image &i ) const {...} };class CheckImageResolution { bool run( Image &i ) const {...} };class RotateImage { bool run( Image &i ) const {...} };
现在所有与克隆相关的代码都位于一个唯一的类中.但是,现在不可能将 ImgOp
的容器模板化为不同的操作:
vector<图像操作>v;//编译错误,ImgOp 不是类型向量<图像运算<ImgOp1>>v;//只有一种操作:/
第 2 步,添加抽象级别
添加一个作为接口的非模板库:
class AbstractImgOp{ImageOp T*克隆()const = 0;bool run(Image &i) const = 0;};模板<类型名称 T >ImgOp 类:公共 AbstractImgOp{//没有修改,特别是在 clone() 方法上,感谢//协变返回类型机制};
现在我们可以写:
vectorv;
但是操作图像操作对象变得很困难:
AbstractImgOp *op1 = new AbstractImgOp;op1->w = ...;//编译错误,AbstractImgOp 没有op2->h = ...;//名为w"或h"的成员CheckImageSize *op1 = 新的 CheckImageSize;op1->w = ...;//美好的op1->h = ...;AbstractImgOp *op1Ptr = op1;//编译错误,CheckImageSize 不导出//来自 AbstractImgOp?令人困惑CheckImageSize op1;op1.w = ...;//美好的op1.h = ...;CheckImageResolution op2;//...v.push_back(new ImgOp(op1));//令人困惑!v.push_back(new ImgOp(op2));//啊
步骤#3,添加克隆指针"类
基于FredOverflow的解决方案,制作了一个克隆指针,使框架更易于使用.
然而,这个指针不需要被模板化,因为它被设计为只保存一种类型的 ptr——只有 ctor 需要被模板化:
class ImgOpCloner{AbstractImgOp *ptr;//Ptr 是实现多态行为所必需的ImgOpCloner &运算符=(const ImgOpCloner & );上市:模板<类型名称 T >ImgOpCloner(const T &t) : ptr(new ImgOp< T >( t ) ) {}ImgOpCloner( const AbstractImgOp &other ) : ptr( other.ptr->clone() ) {}~ImgOpCloner() { 删除 ptr;}AbstractImgOp * operator->() { return ptr;}抽象图像运维运算符*() { 返回 *ptr;}};
现在我们可以写:
CheckImageSize op1;op1.w = ...;//美好的op1.h = ...;CheckImageResolution op2;//...向量
I want to perform "deep copies" of an STL container of pointers to polymorphic classes.
I know about the Prototype design pattern, implemented by means of the Virtual Ctor Idiom, as explained in the C++ FAQ Lite, Item 20.8.
It is simple and straightforward:
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
};
A deep copy is then:
for( i = 0; i < oldVector.size(); ++i )
newVector.push_back( oldVector[i]->clone() );
Drawbacks
As Andrei Alexandrescu states it:
The
clone()
implementation must follow the same pattern in all derived classes; in spite of its repetitive structure, there is no reasonable way to automate defining theclone()
member function (beyond macros, that is).
Moreover, clients of ABC
can possibly do something bad. (I mean, nothing prevents clients to do something bad, so, it will happen.)
Better design?
My question is: is there another way to make an abstract base class clonable without requiring derived classes to write clone-related code? (Helper class? Templates?)
Following is my context. Hopefully, it will help understanding my question.
I am designing a class hierarchy to perform operations on a class Image
:
struct ImgOp
{
virtual ~ImgOp() {}
bool run( Image & ) = 0;
};
Image operations are user-defined: clients of the class hierarchy will implement their own classes derived from 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 { ... };
...
Multiple operations can be performed sequentially on an image:
bool do_operations( vector< ImgOp* > v, Image &i )
{
for_each( v.begin(), v.end(),
/* bind2nd( mem_fun( &ImgOp::run ), i ... ) don't remember syntax */ );
}
If there are multiple images, the set can be split and shared over several threads. To ensure "thread-safety", each thread must have its own copy of all operation objects contained in v
-- v
becomes a prototype to be deep copied in each thread.
Edited: The thread-safe version uses the Prototype design pattern to enforce copy of pointed-to-objects -- not 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 */ );
}
This has sense when image operation classes are small: do not serialize accesses to unique instances of ImgOp
s, rather provide each thread with their own copies.
The hard part is to avoid writers of new ImgOp
-derived classes to write any clone-related code. (Because this is implementation detail -- this is why I dismissed Paul's answers with the Curiously Recurring Pattern.)
FYI, this is the design I came out with. Thank you Paul and FredOverflow for your inputs. (And Martin York for your comment.)
Step #1, Compile-time polymorphism with templates
Polymorphism is performed at compile-time using templates and implicit-interfaces:
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 {...} };
Now all the clone-related code lies within a unique class. However, it is now impossible to have a container of ImgOp
s templatized on different operations:
vector< ImgOp > v; // Compile error, ImgOp is not a type
vector< ImgOp< ImgOp1 > > v; // Only one type of operation :/
Step #2, Add a level of abstraction
Add a non-template base acting as an interface:
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
};
Now we can write:
vector< AbstractImgOp* > v;
But it becomes hard to manipulate image operation objects:
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
Step #3, Add a "cloning pointer" class
Based on the FredOverflow's solution, make a cloning pointer to make the framework simpler to use.
However, this pointer needs not to be templatized for it is designed to hold only one type of ptr -- only the ctor needs to be templatized:
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; }
};
Now we can write:
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
这篇关于C++ 虚构造函数,没有 clone()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!