Переместите назначение, несовместимое со стандартной копией и обменом

StackOverflow https://stackoverflow.com/questions/19841626

Вопрос

Проверка новой семантики движения.

Я только что спросил о проблемах, которые у меня были с конструктором движения. Но, как выясняется в комментариях, проблема действительно в том, что оператор «Перемещение назначения» и оператор «стандартное назначение» при использовании идиомы «Стандартная копия и обмен».

Это класс, который я использую:

#include <string.h>
#include <utility>

class String
{
    int         len;
    char*       data;

    public:
        // Default constructor
        // In Terms of C-String constructor
        String()
            : String("")
        {}

        // Normal constructor that takes a C-String
        String(char const* cString)
            : len(strlen(cString))
            , data(new char[len+1]()) // Allocate and zero memory
        {
            memcpy(data, cString, len);
        }

        // Standard Rule of three
        String(String const& cpy)
            : len(cpy.len)
            , data(new char[len+1]())
        {
            memcpy(data, cpy.data, len);
        }
        String& operator=(String rhs)
        {
            rhs.swap(*this);
            return *this;
        }
        ~String()
        {
            delete [] data;
        }
        // Standard Swap to facilitate rule of three
        void swap(String& other) throw ()
        {
            std::swap(len,  other.len);
            std::swap(data, other.data);
        }

        // New Stuff
        // Move Operators
        String(String&& rhs) throw()
            : len(0)
            , data(null)
        {
            rhs.swap(*this);
        }
        String& operator=(String&& rhs) throw()
        {
            rhs.swap(*this);
            return *this;
        }
};

Довольно болотный стандарт, я думаю.

Затем я так проверил свой код:

int main()
{
    String  a("Hi");
    a   = String("Test Move Assignment");
}

Здесь задание a должен использовать оператор «Перемещение назначения». Но есть столкновение с оператором «стандартного назначения» (который написан в качестве стандартной копии и обмена).

> g++ --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/usr/include/c++/4.2.1
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.0.0
Thread model: posix

> g++ -std=c++11 String.cpp
String.cpp:64:9: error: use of overloaded operator '=' is ambiguous (with operand types 'String' and 'String')
    a   = String("Test Move Assignment");
    ~   ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
String.cpp:32:17: note: candidate function
        String& operator=(String rhs)
                ^
String.cpp:54:17: note: candidate function
        String& operator=(String&& rhs)
                ^

Теперь я могу исправить это, изменив оператор «стандартного назначения» на:

    String& operator=(String const& rhs)
    {
        String copy(rhs);
        copy.swap(*this);
        return *this;
    }

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

Я упускаю что -то не так очевидно?

Это было полезно?

Решение

Если вы определите оператора назначения, чтобы получить значение, вам не нужно (не нужно и не можете) определять оператора назначения, принимающего RVALUE-REFEFERE. Нет смысла в этом.

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

String f();
String a;
a = f();   // with String& operator=(String)

В этом случае компилятор решит призыв a.operator=(f()); он поймет, что единственной причиной возврата является аргумент operator= и выявит любую копию -это то, что функция приобрела ценность!

Другие советы

Другие ответы предлагают иметь только одну перегрузку operator =(String rhs) Принимая аргумент по цене, но это нет Наиболее эффективная реализация.

Это правда, что в этом примере Дэвид Родригес - Dribeas

String f();
String a;
a = f();   // with String& operator=(String)

Копия не сделана. Однако предположим просто operator =(String rhs) предоставляется и рассмотрим этот пример:

String a("Hello"), b("World");
a = b;

Что происходит

  1. b скопирован, чтобы rhs (Распределение памяти + memcpy);
  2. a а также rhs поменяются;
  3. rhs уничтожен.

Если мы реализуем operator =(const String& rhs) а также operator =(String&& rhs) Затем мы можем избежать распределения памяти на шаге 1, когда цель имеет длину больше, чем у источников. Например, это простая реализация (не идеальна: может быть лучше, если String был capacity член):

String& operator=(const String& rhs) {
    if (len < rhs.len) {
        String tmp(rhs);
        swap(tmp);
    else {
        len = rhs.len;
        memcpy(data, rhs.data, len);
        data[len] = 0;
    }
    return *this;
}

String& operator =(String&& rhs) {
    swap(rhs);
}

В дополнение к точке производительности, если swap является noexcept, тогда operator =(String&&) может быть noexcept также. (Что не так, если распределение памяти «потенциально» выполнено.)

Смотрите больше подробностей в этом превосходном объяснение Говард Хиннант.

Все, что вам нужно для копирования и назначения, это следующее:

    // As before
    String(const String& rhs);

    String(String&& rhs)
    :   len(0), data(0)
    {
        rhs.swap(*this);
    }

    String& operator = (String rhs)
    {
        rhs.swap(*this);
        return *this;
    }

   void swap(String& other) noexcept {
       // As before
   }
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top