Идиома копирования и замены все еще полезна в C ++ 11

Я имею в виду этот вопрос:
Что такое идиома копирования и обмена?

По сути, приведенный выше ответ приводит к следующей реализации:

class MyClass
{
public:
friend void swap(MyClass & lhs, MyClass & rhs) noexcept;

MyClass() { /* to implement */ };
virtual ~MyClass() { /* to implement */ };
MyClass(const MyClass & rhs) { /* to implement */ }
MyClass(MyClass && rhs) : MyClass() { swap(*this, rhs); }
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }
};

void swap( MyClass & lhs, MyClass & rhs )
{
using std::swap;
/* to implement */
//swap(rhs.x, lhs.x);
}

Однако обратите внимание, что мы можем полностью отказаться от swap (), выполнив следующее:

class MyClass
{
public:
MyClass() { /* to implement */ };
virtual ~MyClass() { /* to implement */ };
MyClass(const MyClass & rhs) { /* to implement */ }
MyClass(MyClass && rhs) : MyClass() { *this = std::forward<MyClass>(rhs);   }
MyClass & operator=(MyClass rhs)
{
/* put swap code here */
using std::swap;
/* to implement */
//swap(rhs.x, lhs.x);
// :::
return *this;
}
};

Обратите внимание, что это означает, что у нас больше не будет действительного зависимого от аргумента поиска в std :: swap с MyClass.

Короче говоря, есть ли преимущество наличия метода swap ().


редактировать:

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

если оператор = определяется как

MyClass2 & operator=(MyClass2 rhs)

Тогда всякий раз, когда rhs является значением r, вызывается конструктор перемещения. Однако это означает, что при использовании:

MyClass2(MyClass2 && rhs)
{
//*this = std::move(rhs);
}

Заметьте, что в итоге вы получите рекурсивный вызов конструктора перемещения, так как оператор = вызывает конструктор перемещения …

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

Теперь исправить это было бы иметь оба

MyClass2 & operator=(const MyClass2 &rhs)
MyClass2 & operator=(MyClass2 && rhs)

это позволяет нам определить конструкторы копирования как

MyClass2(const MyClass2 & rhs)
{
operator=( rhs );
}

MyClass2(MyClass2 && rhs)
{
operator=( std::move(rhs) );
}

Обратите внимание, что вы пишете одинаковое количество кода, конструкторы копирования приходят «бесплатно», а вы просто пишете operator = (&) вместо конструктора копирования и оператора = (&&) вместо метода swap ().

16

Решение

Прежде всего, вы все равно делаете это неправильно. Идиома copy-and-swap предназначена для повторного использования конструктора для оператора присваивания (а не наоборот), получая прибыль от уже правильно построенного кода конструктора и гарантируя надежную безопасность исключений для оператора присваивания. Но вы не вызываете swap в конструкторе перемещения. Таким же образом конструктор копирования копирует все данные (что бы это ни значило в данном контексте отдельного класса), конструктор перемещения перемещает эти данные, ваш конструктор перемещения создает и назначает / заменяет:

MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }

И это будет в вашей альтернативной версии просто

MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { using std::swap; swap(x, rhs.x); return *this; }

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

Но, чтобы ответить на ваш вопрос, все это все еще идиома копирования и замены, просто без явного swap функция. А в C ++ 11 это еще более полезно, потому что теперь вы реализовали обе копии а также переместить назначение с одной функцией.

Если функция подкачки все еще имеет значение вне оператора присваивания, это совершенно другой вопрос и зависит, может ли этот тип быть заменен в любом случае. Фактически в C ++ 11 типы с правильной семантикой перемещения могут быть просто заменены достаточно эффективно, используя стандартные std::swap реализация, часто устраняя необходимость в дополнительном пользовательском обмене. Только убедитесь, что не называете это значение по умолчанию std::swap внутри вашего оператора присваивания, так как он сам выполняет присваивание перемещения (что приведет к тем же проблемам, что и ваша неправильная реализация конструктора перемещения).

Но, чтобы сказать это снова, обычай swap функция или нет, это ничего не меняет в полезности идиомы копирования и замены, которая еще более полезна в C ++ 11, устраняя необходимость реализации дополнительной функции.

21

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

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

За исключением того, что, поскольку они пишут конструкторы, они могут иметь дело с не конструируемыми по умолчанию типами или типами, значения которых являются плохими, если они не инициализированы явно, как int или просто дороги для конструирования по умолчанию, или если элементы, построенные по умолчанию, недопустимы для разрушения (например, рассмотрите умный указатель — неинициализированный T * приводит к неудачному удалению).

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

2

Обоснованность причин (если таковые имеются) использования идиомы копирования и замены для реализации назначения копирования в C ++ 11 такие же, как и в предыдущих версиях.

Также обратите внимание, что вы должны использовать std::move на переменные-члены в конструкторе перемещения, и вы должны использовать std::move на любые rvalue ссылки, которые являются параметрами функции.

std::forward следует использовать только для ссылок на параметры шаблона в форме T&& а также auto&& переменные (которые могут быть подвергнуты сворачиванию ссылок в ссылки на lvalue во время вывода типа), чтобы сохранить их rvaleness или lvalueness в зависимости от ситуации.

1

для себя сделайте это в C ++ 11 так:

class MyClass {
public:
MyClass(MyString&& pStr) : str(pStr)) {
// call MyString(MyString&& pStr)
}
MyClass(const MyString& pStr) : str(pStr)) {
// call MyString(const MyString& pStr)
}
private:
MyString const str; // Still const!
};
0
По вопросам рекламы [email protected]