Я хочу выполнить "глубокие копии" контейнера STL указателей на полиморфные классы.
Я знаю об Опытном шаблоне разработки, реализованном посредством Виртуальной Идиомы Ctor, как объяснено в Облегченном FAQ C++, Объект 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 заявляет это:
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
s, скорее предоставьте каждому потоку их собственные копии.
Твердая часть должна избежать устройств записи новых ImgOp
- производные классы для написания любого связанного с клоном кода. (Поскольку это - деталь реализации - это - то, почему я отклонил ответы Paul с Любопытно Повторяющимся Шаблоном.)
К вашему сведению, это дизайн, с которым я пришел. Спасибо Paul и FredOverflow за ваш вклад. (И Martin York за ваш комментарий.)
Полиморфизм выполняется во время компиляции с помощью шаблонов и неявных интерфейсов:
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 :/
Добавить нешаблонную базу, действующую как интерфейс:
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
Основываясь на решении 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
Вы можете использовать необычный рекурсивный шаблон, но это может сделать ваш код менее читаемым. Вам все равно понадобятся конструкторы копирования. Это работает следующим образом.
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>
{};
Глубокая копия выглядит так: [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;
}
};
Жюльен: &&
не является «ссылкой на ссылку», это ссылка на rvalue, которая привязывается только к изменяемым rvalue. См. Этот отличный (но, к сожалению, немного устаревший) учебник и видео для обзора ссылок на rvalue и того, как они работают.