Реализовать оператор присваивания С++ с точки зрения конструктора
Фон
Предположим, вы хотите реализовать класс управления ресурсами на C++. Вы не можете использовать правило нуля или правило пяти значений по умолчанию , поэтому вам действительно нужно реализовать конструктор копирования и перемещения, оператор присваивания копирования и перемещения и деструктор. В этом вопросе я буду использоватьв качестве примера, но это может относиться к множеству различных типов.
// Store object on heap but keep value semantics
// Moved-from state has null elem; otherwise should have value
// Only valid operations on moved-from state are assignment and destruction
// Assignment only offers a basic exception guarantee
template <typename T>
class Box {
public:
using value_type = T;
// Default constructor
Box() : elem{new value_type} {}
// Accessor
value_type& get() { return *elem; }
value_type const& get() const { return *elem; }
// Rule of Five
Box(Box const& other) : elem{new value_type{*(other.elem)}} {};
Box(Box&& other) : elem{other.elem}
{
other.elem = nullptr;
};
Box& operator=(Box const& other)
{
if (elem) {
*elem = *(other.elem);
} else {
elem = new value_type{*(other.elem)};
}
return *this;
}
Box& operator=(Box&& other)
{
delete elem;
elem = other.elem;
other.elem = nullptr;
return *this;
}
~Box()
{
delete elem;
}
// Swap
friend void swap(Box& lhs, Box& rhs)
{
using std::swap;
swap(lhs.elem, rhs.elem);
}
private:
T* elem;
};
(Обратите внимание, что лучшая реализация будет иметь больше функций, таких как
noexcept
и функции,
explicit
перенаправление конструкторов на основе
value_type
конструкторы ʼs, поддержка распределителей и т. д.; здесь я реализую только то, что необходимо для вопроса и тестов. Это также может сохранить некоторый код с помощью
std::unique_ptr
, но это сделало бы его менее наглядным примером)
Обратите внимание, что операторы присваивания совместно используют много кода друг с другом, с соответствующими конструкторами и с деструктором. Это было бы несколько уменьшено, если бы я не хотел разрешать присваивание перемещению из en, но это было бы более очевидно в более сложном классе.
Отступление: копирование и замена
Один из стандартных способов решения этой проблемы — использование идиомы копирования и замены (также называемой в данном контексте правилом четырех с половиной) , которая также дает вам надежную гарантию исключения, если
nothrow
:
// Copy and Swap (Rule of Four and a Half)
Box& operator=(Box other) // Take `other` by value
{
swap(*this, other);
return *this;
}
Это позволяет написать только один оператор присваивания (взяв
other
по значению позволяет компилятору переместить другой параметр в параметр, если это возможно, и при необходимости выполняет копирование за вас), и этот оператор присваивания прост (при условии, что у вас уже есть
swap
). Однако, как говорится в связанной статье, у этого есть проблемы, такие как дополнительное выделение и хранение дополнительных копий содержимого во время операции.
Идея
Чего я раньше не видел, так это того, что я назову оператором присваивания «Уничтожить и инициализировать». Поскольку мы уже делаем всю работу в конструкторе, а версия объекта, для которой назначено, должна быть идентична версии, созданной путем копирования, почему бы не использовать конструктор следующим образом:
// Destroy-and-Initialize Assignment Operator
template <typename Other>
Box& operator=(Other&& other)
requires (std::is_same_v<Box, std::remove_cvref_t<Other>>)
{
this->~Box();
new (this) Box{std::forward<Other>(other)};
return *std::launder(this);
}
Это по-прежнему делает дополнительное распределение, как это делает Copy-and-Swap, но только в случае назначения копирования вместо случая назначения перемещения, и оно делает это после уничтожения одной из копий
T
, поэтому он не дает сбоев при ограничении ресурсов.
Вопросы
- Предлагалось ли это раньше, и если да, то где я могу прочитать об этом подробнее?
- Является ли это UB в некоторых случаях, например, если
Box
является подобъектом чего-то другого или разрешено уничтожать и перестраивать подобъекты? - Есть ли у этого недостатки, о которых я не упомянул, например,
constexpr
совместимый? - Существуют ли другие способы избежать повторного использования кода оператора присваивания, такие как этот и правило четырех с половиной, когда вы не можете просто
= default
их?