Как подходить к копированию объектов с помощью умных указателей в качестве атрибутов класса?

От повысить документацию библиотеки Я прочитал это:

Концептуально умные указатели рассматриваются как владеющие объектом, на который
и, таким образом, отвечает за удаление объекта, когда он больше не
необходимо.

У меня очень простая проблема: я хочу использовать RAII для атрибутов указателя класса, который можно копировать и назначать.

Операции копирования и присваивания должны быть глубокими: каждый объект должен иметь свою собственную копию фактических данных. Кроме того, RTTI должен быть доступен для атрибутов (их тип также может быть определен во время выполнения).

Должен ли я искать реализацию копируемого интеллектуального указателя (данные небольшие, поэтому мне не нужно Копирование при записи указатели), или я должен делегировать операцию копирования конструкторам копирования моих объектов, как показано в этот ответ?

Какой умный указатель выбрать для простого RAII класса, который можно копировать и назначать? (Я думаю, что unique_ptr с делегированными операциями копирования / присваивания конструктору копирования класса и оператору присваивания сделает правильный выбор, но я не уверен)

Вот псевдокод для проблемы с использованием необработанных указателей, это просто описание проблемы, а не работающий код C ++:

// Operation interface
class ModelOperation
{
public:
virtual void operate = ();
};

// Implementation of an operation called Special
class SpecialModelOperation
:
public ModelOperation
{
private:
// Private attributes are present here in a real implementation.

public:

// Implement operation
void operate () {};
};

// All operations conform to ModelOperation interface
// These are possible operation names:
// class MoreSpecialOperation;
// class DifferentOperation;

// Concrete model with different operations
class MyModel
{
private:
ModelOperation* firstOperation_;
ModelOperation* secondOperation_;

public:

MyModel()
:
firstOperation_(0),
secondOperation_(0)
{
// Forgetting about run-time type definition from input files here.
firstOperation_  = new MoreSpecialOperation();
secondOperation_ = new DifferentOperation();
}

void operate()
{
firstOperation_->operate();
secondOperation_->operate();
}

~MyModel()
{
delete firstOperation_;
firstOperation_ = 0;

delete secondOperation_;
secondOperation_ = 0;
}
};

int main()
{

MyModel modelOne;

// Some internal scope
{
// I want modelTwo to have its own set of copied, not referenced
// operations, and at the same time I need RAII to for the operations,
// deleting them automatically as soon as it goes out of scope.
// This saves me from writing destructors for different concrete models.
MyModel modelTwo (modelOne);
}return 0;
}

7

Решение

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

Если это приемлемо, можно пойти по этому пути, удалив производные типы в интерфейсе, который предоставляет функции копирования:

template <typename Base>
struct clonable {
// virtual copy
// this clone function will be generated via templates
// no boilerplate is involved
virtual std::unique_ptr<clonable<Base>> clone() const = 0;

// expose the actual data
virtual Base* get() = 0;
virtual Base const* get() const = 0;

// virtual destructor
// note that this also obviates the need for a virtual destructor on Base
// I would probably still make it virtual, though, just in case
virtual ~clonable() = default;
};

Этот интерфейс реализован классом, основанным на наиболее производном типе, и, таким образом, знает, как делать нормальные копии через конструктор копирования.

template <typename Base, typename Derived>
struct clonable_holder : clonable<Base> {
// I suppose other constructors could be provided
// like a forwarding one for emplacing, but I am going for minimal here
clonable_holder(Derived value)
: storage(std::move(value)) {}

// here we know the most derived type, so we can use the copy constructor
// without risk of slicing
std::unique_ptr<clonable<Base>> clone() const override {
return std::unique_ptr<clonable<Base>>(new clonable_holder(storage));
}

Base* get() override { return &storage; }
Base const* get() const override { return &storage; }

private:
Derived storage;
};

Это создаст для нас виртуальные функции копирования без лишних шаблонов. Теперь мы можем построить интеллектуальный класс, похожий на указатель (не совсем интеллектуальный указатель, потому что он не дает семантику указателя, а вместо этого семантику значения).

template <typename Base>
struct polymorphic_value {
// this constructor captures the most derived type and erases it
// this is a point where slicing may still occur
// so making it explicit may be desirable
// we could force constructions through a forwarding factory class for extra safety
template <typename Derived>
polymorphic_value(Derived value)
: handle(new clonable_holder<Base, Derived>(std::move(value))) {
static_assert(std::is_base_of<Base, Derived>::value,
"value must be derived from Base");
}

// moving is free thanks to unique_ptr
polymorphic_value(polymorphic_value&&) = default;
polymorphic_value& operator=(polymorphic_value&&) = default;

// copying uses our virtual interface
polymorphic_value(polymorphic_value const& that)
: handle(that.handle->clone()) {}
polymorphic_value& operator=(polymorphic_value const& that) {
handle = that.handle->clone();
return *this;
}

// other useful constructors involve upcasting and so on

// and then useful stuff for actually using the value
Base* operator->() { return handle.get(); }
Base const* operator->() const { return handle.get(); }
// ...

private:
std::unique_ptr<clonable<Base>> handle;
};

Это просто минимальный интерфейс, но его можно легко уточнить, чтобы охватить больше сценариев использования.

5

Другие решения

Уже немного поздно, но для будущих зрителей: в моей библиотеке только для заголовков есть готовая к использованию реализация Аврора И его SmartPtr учебник. С Aurora легко реализовать глубокое копирование с помощью умных указателей. Следующий код работает для любого копируемого типа T:

aurora::CopiedPtr<T> first(new T);
aurora::CopiedPtr<T> second = first; // deep copy

Это часто делает ненужной реализацию The Big Three / Five, если у ваших классов есть указатели.

4

Похоже, нужно иметь возможность создавать умный указатель, который создает новую копию объекта каждый раз, когда создается другой объект умного указателя. (Полагаю, является ли эта копия «глубокой» или нет, зависит от конструктора объекта; хранящиеся у вас объекты могут иметь многоуровневую собственность, насколько нам известно, поэтому «глубокая» зависит от значения Объекты. Главное для наших целей — это то, что вам нужно что-то, что создает отдельный объект, когда умный указатель создается с помощью ссылки из другого, а не просто извлекает указатель на существующий объект.)

Если я правильно понял вопрос, то вам потребуется метод виртуального клона. Нет другого способа правильно вызвать конструктор производного класса.

struct Clonable {
virtual ~Clonable() {}
virtual Clonable* clone() = 0;
};
struct AutoPtrClonable {
AutoPtrClonable(Clonable* cl=0) : obj(cl) { }
AutoPtrClonable(const AutoPtrClonable& apc) : obj(apc.obj->clone()) { }
~AutoPtrClonable() { delete obj; }
// operator->, operator*, etc
Clonable* obj;
};

Чтобы использовать пример кода, превратить его в шаблон и т. Д.

1

Я никогда не слышал о готовой реализации, но вы можете просто сделать это самостоятельно.

Прежде всего, вы должны написать некоторый класс-оболочку шаблона, который имеет метод виртуального клона, возвращающий копию хранимого объекта. А потом напишите какой-нибудь полимофический держатель этого класса, который можно было бы скопировать

и не забудьте о проверенном удалении
http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Checked_delete

1

У вас есть два решения (на самом деле у вас их гораздо больше, но они имеют для меня наибольшее значение :)):

Во-первых, вы можете использовать std::unique_ptr, Это хорошее решение, потому что оно заставляет вас иметь один экземпляр на указатель. (с помощью std::shared_ptr вместо этого будет также работать, но если вы не добавите код явно, копирование и назначение для shared_ptr будут «делиться» — особенно то, что вы хотите избежать).

Если вы используете std::unique_ptrваш конструктор копирования и оператор присваивания должны явно делать глубокое копирование (используя clone метод в интерфейсе получателя или новый оператор в вызове unique_ptr конструктор).

Во-вторых, вы можете свернуть свои собственные. В этом нет ничего сложного, и мы говорим о небольшом (10-20 строк или около того) служебном классе.

Лично, если бы мне пришлось использовать этот класс интеллектуальных указателей в одном месте, я бы использовал std :: unique_ptr. В противном случае (несколько указателей, одно и то же поведение) я бы бросил свой собственный, просто чтобы мне не пришлось повторять глубокую копию во многих случаях (следуя принципу СУХОЙ).

0
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector