C ++ Virtual Constructor, без клонирования () - PullRequest
8 голосов
/ 04 мая 2010

Я хочу выполнить «глубокие копии» контейнера 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, для написания любого кода, связанного с клоном. (Потому что это детали реализации - вот почему я отклонил ответы Пола с любопытно повторяющимся шаблоном.)

Ответы [ 3 ]

7 голосов
/ 04 мая 2010

Вы можете использовать странно рекурсивный шаблон, но он может сделать ваш код менее читабельным. Вам все еще понадобятся конструкторы копирования. Работает следующим образом.

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>
{};
2 голосов
/ 10 мая 2010

К вашему сведению, это дизайн, который я разработал. Спасибо Пол и FredOverflow за ваш вклад. (И Мартин Йорк за ваш комментарий.)

Шаг # 1, полиморфизм времени компиляции с шаблонами

Полиморфизм выполняется во время компиляции с использованием шаблонов и 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 {...} };

Теперь весь код, связанный с клоном, находится внутри уникального класса. Однако теперь невозможно иметь контейнер с шаблоном ImgOp s для различных операций:

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
1 голос
/ 04 мая 2010

Глубокая копия: [для цикла]

Вы заставляете клиента клонировать вектор явно. Я не уверен, что это ответит на ваш вопрос, но я бы предложил вектор умных указателей, чтобы клонирование происходило автоматически.

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

Julien: && это не «ссылка на ссылку», это ссылка на rvalue, которая связывается только с модифицируемыми значениями r. Посмотрите этот превосходный (но, к сожалению, немного устаревший) учебник и video для обзора ссылок на rvalue и их работы.

...