Является ли идиома копирования и свопинга еще полезной в С++ 11 - программирование
Подтвердить что ты не робот

Является ли идиома копирования и свопинга еще полезной в С++ 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);
}

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

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

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

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

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

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

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

Обратите внимание, что вы пишете одинаковый объем кода, конструкторы копирования становятся "свободными", и вы просто пишете operator = (&) вместо конструктора копирования и оператора = (& &) вместо свопа().

4b9b3361

Ответ 1

Прежде всего, вы все равно ошибаетесь. Идиома "копирование и своп" позволяет повторно использовать конструктор для оператора присваивания (а не наоборот), получая прибыль от уже правильного построения кода конструктора и гарантируя надежную защиту исключений для оператора присваивания. Но вы не вызываете 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; }

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

для mebest сделать это в С++ 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!
};