Pregunta

Probar la nueva semántica de movimiento.

Solo pregunté sobre los problemas que estaba teniendo con el constructor de movimiento. Pero resulta que en los comentarios, el problema es realmente que el operador de "asignación de movimiento" y el choque del operador de "asignación estándar" cuando usa el idioma estándar "Copia e intercambio".

Esta es la clase que estoy usando:

#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;
        }
};

Bastante estándar de pantano, creo.

Luego probé mi código así:

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

Aquí la tarea a a debe usar el operador de "asignación de movimiento". Pero hay un choque con el operador de "asignación estándar" (que se escribe como su copia y intercambio estándar).

> 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)
                ^

Ahora puedo solucionar esto modificando el operador "Asignación estándar" a:

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

Pero esto no es bueno, ya que se mete con la capacidad del compilador para optimizar la copia y el intercambio. ¿Ves cuál es el idioma de copia y intercambio? aquí y aquí

¿Me estoy perdiendo algo no tan obvio?

¿Fue útil?

Solución

Si define el operador de asignación para que tome un valor, no debe (no necesita y no puede) definir el operador de asignación que toma una referencia de RValue. No tiene sentido.

En general, solo necesita proporcionar una sobrecarga que tome una referencia de RValue cuando necesita diferenciar un LValue de un rValue, pero en este caso su elección de implementación significa que no necesita hacer esa distinción. Ya sea que tenga un Lvalue o un Rvalue, creará el argumento e intercambiará el contenido.

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

En este caso, el compilador resolverá la llamada para ser a.operator=(f()); se dará cuenta de que la única razón del valor de retorno es ser el argumento para operator= Y elirá cualquier copia, ¡este es el punto de hacer que la función tome un valor en primer lugar!

Otros consejos

Otras respuestas sugieren tener solo una sobrecarga operator =(String rhs) Tomando el argumento por valor pero esto es no la implementación más eficiente.

Es cierto que en este ejemplo de David Rodríguez - Dribeas

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

No se hace una copia. Sin embargo, suponga solo operator =(String rhs) se proporciona y considere este ejemplo:

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

Lo que sucede es

  1. b se copia a rhs (Asignación de memoria + memcpy);
  2. a y rhs están intercambiados;
  3. rhs Esta destruido.

Si implementamos operator =(const String& rhs) y operator =(String&& rhs) Luego podemos evitar la asignación de memoria en el paso 1 cuando el objetivo tiene una longitud más grande que la fuente. Por ejemplo, esta es una implementación simple (no perfecta: podría ser mejor si String tener un capacity miembro):

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);
}

Además del punto de rendimiento si swap es noexcept, después operator =(String&&) puede ser noexcept también. (Que no es el caso si la asignación de memoria es "potencialmente" realizada).

Ver más detalles en este excelente explicación por Howard Hinnant.

Todo lo que necesita para la copia y la tarea es esta:

    // 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
   }
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top