C++虚拟构造函数,没有clone()
我想对指向多态类的指针的STL容器执行“深层复制”。
我了解通过Virtual Ctor Idiom实现的原型设计模式,如C++ FAQ Lite,项目 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() );
缺点
As Andrei Alexandrescu
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
成为原型在每个线程中进行深度复制。
编辑:线程安全版本使用原型设计模式来强制复制指向对象——而不是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 派生类的编写者编写任何与克隆相关的代码。 (因为这是实现细节——这就是为什么我用奇怪的重复模式驳回了保罗的答案。)
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.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
您可以使用奇怪的递归模式,但它可能会使您的代码可读性较差。
您仍然需要复制构造函数。其工作原理如下。
You can use the curiously recursive pattern but it might make your code less readable.
You will still need copy constructors. It works as follows.
让客户端显式克隆向量。我不确定这是否回答了您的问题,但我建议使用智能指针向量,以便克隆自动发生。
当然,您不希望在调整向量或其他内容的大小时发生这些隐式副本,因此您需要能够区分副本和移动。这是我的
cloning_pointer
的 C++0x 玩具实现,您可能需要根据您的需要进行调整。Julien:
&&
不是“ref of ref”,它是一个右值引用,仅绑定到可修改的右值。看到这个优秀的(但遗憾的是有点过时)教程 和 视频 了解右值引用及其工作原理的概述。You make the client clone the vector explicitly. I'm not sure if this answers your question, but I would suggest a vector of smart pointers so the cloning happens automatically.
Of course, you don't want these implicit copies to happen when resizing a vector or something, so you need to be able to distinguish copies from moves. Here is my C++0x toy implementation of
cloning_pointer
which you might have to adjust to your needs.Julien:
&&
is not a "ref of ref", it is an rvalue reference which only binds to modifiable rvalues. See this excellent (but sadly slightly outdated) tutorial and video for an overview of rvalue references and how they work.仅供参考,这是我提出的设计。感谢 Paul 和 FredOverflow 的投入。 (还有 Martin York 的评论。)
步骤#1,使用模板的编译时多态性
多态性是在编译时使用模板和隐式接口执行的:
现在所有与克隆相关的代码都位于独特的班级。然而,现在不可能在不同的操作上模板化一个 ImgOp 容器:
步骤#2,添加抽象级别
添加充当接口的非模板库:
现在我们可以编写:
但操作图像操作对象变得困难:
步骤#3,添加一个“克隆指针”类
基于FredOverflow的解决方案,制作一个克隆指针,使框架更简单使用。
然而,这个指针不需要模板化,因为它被设计为只保存一种类型的 ptr ——只有 ctor 需要模板化:
现在我们可以写:
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:
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:Step #2, Add a level of abstraction
Add a non-template base acting as an interface:
Now we can write:
But it becomes hard to manipulate image operation objects:
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:
Now we can write: