Я хочу выполнить «глубокие копии» контейнера 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() );
1018 * Недостатки *
Как утверждает Андрей Александреску :
Реализация 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
s, а предоставьте каждому потоку свои копии.
Сложная задача - избежать написания новых классов, полученных из ImgOp
, для написания любого кода, связанного с клоном. (Потому что это детали реализации - вот почему я отклонил ответы Пола с любопытно повторяющимся шаблоном.)