Question

Test de la nouvelle sémantique.

Je viens de poser des questions sur les problèmes que j'avais avec le constructeur de déménagement. Mais il s'avère que dans les commentaires, le problème est vraiment que l'opérateur "Move Assignment" et l'opérateur "Assignment standard" s'affrontent lorsque vous utilisez l'idiome "Copier et échanger" standard.

C'est la classe que j'utilise:

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

Jolie de la tourbière, je pense.

Ensuite, j'ai testé mon code comme ceci:

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

Ici, la mission à a doit utiliser l'opérateur "Move Assignment". Mais il y a un affrontement avec l'opérateur "Affectation standard" (qui est écrit comme votre copie et échange 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)
                ^

Maintenant, je peux résoudre ce problème en modifiant l'opérateur "Affectation standard" à:

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

Mais ce n'est pas bon car il gâche la capacité du compilateur à optimiser la copie et l'échange. Vous voyez quel est l'idiome de copie-échange? ici et ici

Est-ce que je manque quelque chose de si évident?

Était-ce utile?

La solution

Si vous définissez l'opérateur d'affectation pour prendre une valeur, vous ne devez pas (ne pas avoir besoin) de définir l'opérateur d'affectation prenant une référence RValue. Il n'y a pas de problème à cela.

En général, il vous suffit de fournir une surcharge en prenant une référence RValue lorsque vous avez besoin de différencier un LVALUE d'une réalité, mais dans ce cas, votre choix de mise en œuvre signifie que vous n'avez pas besoin de faire cette distinction. Que vous ayez un LVALUE ou un RValue, vous allez créer l'argument et échanger le contenu.

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

Dans ce cas, le compilateur résoudra l'appel pour être a.operator=(f()); Il se rendra compte que la seule raison de la valeur de retour est l'argument operator= et élirera n'importe quelle copie - c'est le but de faire en sorte que la fonction prenne une valeur en premier lieu!

Autres conseils

D'autres réponses suggèrent d'avoir une seule surcharge operator =(String rhs) prendre l'argument par valeur mais c'est ne pas la mise en œuvre la plus efficace.

Il est vrai que dans cet exemple de David Rodríguez - Dribeas

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

aucune copie n'est faite. Cependant, supposons juste operator =(String rhs) est fourni et considérez cet exemple:

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

Ce qui se produit est

  1. b est copié sur rhs (allocation de mémoire + memcpy);
  2. a et rhs sont échangés;
  3. rhs est détruit.

Si nous mettons en œuvre operator =(const String& rhs) et operator =(String&& rhs) Ensuite, nous pouvons éviter l'allocation de mémoire à l'étape 1 lorsque la cible a une longueur plus grande que celle de la source. Par exemple, il s'agit d'une implémentation simple (pas parfaite: pourrait être mieux si String avait un capacity membre):

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

En plus du point de performance si swap est noexcept, alors operator =(String&&) peut être noexcept aussi bien. (Ce qui n'est pas le cas si l'allocation de mémoire est "potentiellement" effectuée.)

Voir plus de détails dans cet excellent explication par Howard Hinnant.

Tout ce dont vous avez besoin pour la copie et la mission est le suivant:

    // 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
   }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top