C++ Виртуальный Конструктор, без клона ()

Я хочу выполнить "глубокие копии" контейнера 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 */ );
}

Это имеет смысл, когда операционные классы изображения являются маленькими: не сериализируйте доступы к уникальным экземплярам ImgOps, скорее предоставьте каждому потоку их собственные копии.

Твердая часть должна избежать устройств записи новых ImgOp- производные классы для написания любого связанного с клоном кода. (Поскольку это - деталь реализации - это - то, почему я отклонил ответы Paul с Любопытно Повторяющимся Шаблоном.)

8
задан Julien-L 10 May 2010 в 07:58
поделиться

3 ответа

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

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

Полиморфизм выполняется во время компиляции с помощью шаблонов и неявных интерфейсов:

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 :/

Шаг #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 - только 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
2
ответ дан 5 December 2019 в 17:35
поделиться

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

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>
{};
7
ответ дан 5 December 2019 в 17:35
поделиться

Глубокая копия выглядит так: [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 и того, как они работают.

1
ответ дан 5 December 2019 в 17:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: