Domanda

Testare la nuova semantica della mossa.

Ho appena chiesto problemi che stavo riscontrando con il costruttore di mosse. Ma a quanto pare nei commenti, il problema è davvero che l'operatore "spostamento dell'assegnazione" e lo scontro dell'operatore "Assegnazione standard" quando si utilizza il linguaggio standard "copia e scambia".

Questa è la classe che sto 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;
        }
};

Bog standard, penso.

Poi ho testato il mio codice in questo modo:

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

Qui l'incarico a a dovrebbe utilizzare l'operatore "spostamento dell'assegnazione". Ma c'è uno scontro con l'operatore "Assegnazione standard" (che è scritto come copia e swap standard).

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

Ora posso risolvere questo problema modificando l'operatore "Assegnazione standard" a:

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

Ma questo non è buono in quanto confonde con la capacità del compilatore di ottimizzare la copia e lo scambio. Vedi cos'è il linguaggio di copia e swap? qui e qui

Mi manca qualcosa di non ovvio?

È stato utile?

Soluzione

Se si definisce l'operatore di assegnazione per assumere un valore, non è necessario (non è necessario e non puoi) definire l'operatore di assegnazione che assume una riferimento RValue. Non ha senso.

In generale, devi solo fornire un sovraccarico che assume una riferimento per il rValue quando è necessario differenziare un lValue da un rValue, ma in questo caso la tua scelta di implementazione significa che non è necessario fare tale distinzione. Che tu abbia un LVALUE o un RVALUE, stai per creare l'argomento e scambiare il contenuto.

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

In questo caso, il compilatore risolverà la chiamata per essere a.operator=(f()); si renderà conto che l'unica ragione per il valore di ritorno è essere l'argomento operator= E eliminerà qualsiasi copia: questo è il punto di far sì che la funzione prenda un valore in primo luogo!

Altri suggerimenti

Altre risposte suggeriscono di avere un solo sovraccarico operator =(String rhs) prendendo l'argomento in valore ma questo è non l'implementazione più efficiente.

È vero che in questo esempio di David Rodríguez - Dribeas

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

Nessuna copia è fatta. Tuttavia, supponiamo solo operator =(String rhs) viene fornito e considera questo esempio:

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

Quello che succede è

  1. b è copiato a rhs (Allocazione della memoria + memcpy);
  2. a e rhs sono scambiati;
  3. rhs è distrutto.

Se implementiamo operator =(const String& rhs) e operator =(String&& rhs) Quindi possiamo evitare l'allocazione della memoria al passaggio 1 quando il target ha una lunghezza più grande di quella della fonte. Ad esempio, questa è una semplice implementazione (non perfetta: potrebbe essere migliore se String aveva un capacity membro):

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

Oltre al punto di prestazione se swap è noexcept, poi operator =(String&&) può essere noexcept anche. (Il che non è il caso se l'allocazione della memoria è "potenzialmente" eseguita.)

Vedi maggiori dettagli in questo eccellente spiegazione di Howard Hinnant.

Tutto ciò che serve per copia e assegnazione è questo:

    // 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
   }
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top