Делегирование конструктора перемещения по умолчанию - программирование
Подтвердить что ты не робот

Делегирование конструктора перемещения по умолчанию

Я часто нахожу, что я писал утомительные конструкторы перемещения для классов со многими переменными-членами. Они выглядят примерно так:

A(A && rhs) :
    a(std::move(rhs.a)),
    b(std::move(rhs.b)),
    c(std::move(rhs.c)),
    d(std::move(rhs.d)) {
    some_extra_work();
}

То есть они выполняют все действия, связанные с конструктором перемещения по умолчанию, а затем формируют какую-то обыденную дополнительную задачу. В идеале я бы делегировал конструктору перемещения по умолчанию, а затем выполнял дополнительную работу, однако действие по определению моего собственного конструктора перемещения предотвращает определение реализации по умолчанию, то есть там нечего делегировать.

Есть ли хороший способ обойти этот анти-шаблон?

4b9b3361

Ответ 1

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

Оберните дополнительную работу в новый тип и унаследуйте от него:

class A;

struct EW
{
    EW(EW&&);
};

class A : private EW
{
    friend class EW;
public:
    A(A&&) = default;
};

EW::EW(EW&&) { A* self = static_cast<A*>(this); self->some_extra_work(); }

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

Это не будет работать, если some_extra_work() должно быть выполнено после инициализации членов, потому что базовые классы инициализируются первыми.

В качестве альтернативы, если дополнительная работа на самом деле работает с объектом rvalue, из которого вы перемещаетесь, то вам следует обернуть элементы в типы, которые выполняют эту работу автоматически при перемещении из, например, мой тип tidy_ptr, который я использую для реализации правила нуля

class A
{
    tidy_ptr<D> d;
public:
    A() = default;
    A(const A&) = default;
    A(A&& r) = default;  // Postcondition: r.d == nullptr
};