Это правильное применение правила пяти с абстрактным базовым классом и членом unique_ptr?

Попытка устранить ошибку C2248 связанных с абстрактным базовым классом, использующим реализацию операторов копирования / перемещения / операторов присваивания и dtor (правило пяти), и возникает несколько вопросов:

1) Почему правило 5, в первую очередь относящееся к dtor, применяется, когда элементы данных unique_ptr обрабатываются автоматически? Реализация dtor должна быть оставлена ​​правильной, так как unique_ptrs автоматически уничтожается, когда их владельцы выходят из области видимости?

2) Предположим, что другой класс имеет член типа std :: unique_ptr вектора того же типа. Для того чтобы этот класс был копируемым, он должен иметь ctor и оператор копирования, которые клонируют элемент данных unique_ptr? Я видел это решение, но похоже, что оригинальный постер только что переключился на shared_ptr ради удаления ошибки без особого внимания к управлению собственностью. Это правильная стратегия?

3) Рассмотрим тот же случай, что и в вопросе 2 выше, касающемся вектора unique_ptr. Должен ли dtor включать вызов clear () вектора?

4) Операторы присваивания для Derived1 не верны. Но в базовом классе должны быть операторы копирования и перемещения, поскольку в нем есть операторы копирования / перемещения (правило 4/5). Они не могут быть использованы вне класса, так как он абстрактный и, следовательно, экземпляры не будут назначены. Но как мне использовать этот код из производных классов? Каждый производный класс должен иметь возможность перемещать / копировать базовые элементы данных и свои собственные элементы данных. Я не уверен, что делать.

    #include <algorithm>
#include <memory>
#include <vector>
#include <iostream>

class Base{

public:
Base() : m_subBases(){};

/*  copy ctor */
Base(const Base& other) : m_subBases(){
*this = other;
};

/*  move ctor */
Base(Base&& other) : m_subBases(){
*this =std::move( other);
};

/*  Move assignment operator*/
Base& operator=(Base&& other){
m_subBases = std::move(other.m_subBases);
return *this;
};

/*  Copy assignment operator */
Base& operator=(const Base& other){
for(int i = 0; i < other.m_subBases.size(); i++)
m_subBases.push_back(other.m_subBases[i]->clone());

return *this;
};

/* virtual dtor */
virtual ~Base(){
m_subBases.clear();
};

/* Used for creating clones of unique_ptrs */
virtual std::unique_ptr <Base> clone() const= 0;

/* Do something */
virtual void execute(float f) = 0;

//Omitted data member access methods

protected:
std::vector < std::unique_ptr <Base> > m_subBases;
};

class Derived1 : public Base{

public:
Derived1() :  Base(){};

/*  copy ctor */
Derived1(const Derived1& other) : Base(other){
*this = other;
};

/*  move ctor */
Derived1(Derived1&& other) : Base(std::move(other)){
*this = std::move(other);
};

/*  Move assignment operator*/
Derived1& operator=(Derived1&& other){

//This is redundant when called in the move ctor because
// of the call to Base(std::move(other))
m_subBases = std::move(other.m_subBases);

m_string = other.m_string;
return *this;
};

/*  Copy assignment operator */
Derived1& operator=( const Derived1& other){

//This is redundant when called in the copy ctor because
// of the call to Base(other)
for(int i = 0; i < other.m_subBases.size(); i++)
m_subBases.push_back(other.m_subBases[i]->clone());

m_string = other.m_string;
return *this;
};

/* virtual dtor */
virtual ~Derived1(){};

/* Used for creating clones of unique_ptrs */
virtual std::unique_ptr <Base> clone() const{
return std::unique_ptr <Base> (new Derived1(*this));
};

virtual void execute(float f){
std::cout << "Derived1 " << f << std::endl;
};
protected:

std::string m_string;
};

-1

Решение

Я хотел бы предложить альтернативный подход. Не Страшное Правило Пяти, а Приятное Нулевое Правило, как уже говорил @Tony The Lion. Полная реализация моего предложения была написана несколькими людьми, и есть хорошая версия в @Р. Библиотека Мартино Фернандеса, но я представлю упрощенную версию.

Для начала давайте подведем итоги:

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

Есть очевидное предостережение: когда вы дизайн класс одиночной ответственности, вы, конечно, должны подчиняться:

Правило пяти: Если вы пишете какой-либо из операторов копирования или перемещения, оператора назначения копирования или перемещения или деструктора, вы должны реализовать все пять. (Но «пять» функций, необходимых для этого правила, на самом деле: Destructor, Copy-Const, Move-Const, Assignment и Swap.)

Давай сделаем это. Во-первых, ваш потребитель:

struct X;

struct Base
{
std::vector<value_ptr<X>> v;
};

struct Derived : Base
{
};

Обратите внимание, что оба Base а также Derived подчиняйся правилу нуля!

Все, что нам нужно сделать, это реализовать value_ptr, Если pointee не является полиморфным, подойдет следующее:

template <typename T>
class value_ptr
{
T * ptr;
public:
// Constructors
constexpr value_ptr()      noexcept : ptr(nullptr) { }
constexpr value_ptr(T * p) noexcept : ptr(p)       { }

// Rule of Five begins here:
~value_ptr() { ::delete ptr; }
value_ptr(value_ptr const & rhs) : ptr(rhs.ptr ? ::new T(*rhs.ptr) : nullptr) { }
value_ptr(value_ptr && rhs) noexcept : ptr(rhs.ptr) { rhs.ptr = nullptr; }
value_ptr & operator=(value_ptr rhs) { swap(rhs); return *this; }
void swap(value_ptr & rhs) noexcept { std::swap(rhs.ptr, ptr); }

// Pointer stuff
T & operator*() const noexcept { return *ptr; }
T * operator->() const noexcept { return ptr; }
};

template <typename T, typename ...Args>
value_ptr<T> make_value(Args &&... args)
{
return value_ptr<T>(::new T(std::forward<Args>(args)...));
}

Если вам нужен интеллектуальный указатель, который обрабатывает полиморфные указатели базового класса, я предлагаю вам требовать, чтобы ваш базовый класс предоставлял виртуальный clone() функция, и что вы реализуете clone_ptr<T>, чей конструктор копирования будет выглядеть так:

clone_ptr(clone_ptr const & rhs) : ptr(rhs.ptr ? rhs.ptr->clone() : nullptr) { }
2

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

Других решений пока нет …

По вопросам рекламы [email protected]