Copie constructeur est appelé au lieu de constructeur de mouvement - pourquoi?

voix
0

J'ai ce code, pris d'ici par la voie http://www.cplusplus.com/doc/tutorial/classes2/

// move constructor/assignment
#include <iostream>
#include <string>
#include <utility>
using namespace std;

class Example6
{
    string* ptr;
public:
    Example6(const string& str) :
            ptr(new string(str))
    {
        cout << DONT   MOVE  << '\n';
    }
    ~Example6()
    {
        delete ptr;
    }
// move constructor
    Example6(Example6&& x) :
            ptr(x.ptr)
    {
        cout << MOVE  << '\n';
        x.ptr = nullptr;
    }
// move assignment
    Example6& operator=(Example6&& x)
    {
        delete ptr;
        ptr = x.ptr;
        x.ptr = nullptr;
        return *this;
    }
// access content:
    const string& content() const
    {
        return *ptr;
    }
// addition:
    Example6 operator+(const Example6& rhs)
    {
        return Example6(content() + rhs.content());
    }
};

int main()
{
    Example6 foo(Exam);
    Example6 bar = Example6(ple); // move-construction

    foo = foo + bar; // move-assignment

    cout << foo's content:  << foo.content() << '\n';
    return 0;
}

Je ne sortie ajouté dans le constructeur pour voir qui est appelé. À ma grande surprise, il est toujours le premier, constructeur par recopie. Pourquoi est-il arrivé? Je l'ai fait quelques recherches et trouvé quelques informations sur élision. Est-il possible d'éviter une certaine façon et toujours appeler constructeur déplacer?

En outre, comme une note de côté, comme je l'ai dit ce code est de cplusplus.com. Cependant, je l'ai lu sur la sémantique de se déplacer dans d'autres endroits et je me demande si ce constructeur de mouvement se fait ici. Il ne devrait pas appeler

  ptr(move(x.ptr))

au lieu de simplement

ptr(x.ptr)

La façon dont je comprends cela, si nous utilisons la deuxième option, nous appelons constructeur de copie de la chaîne, au lieu de se déplacer, parce que x est référence rvalue qui a un nom, il est donc vraiment lvalue et nous devons utiliser mouvement pour le lancer être rvalue. Est-ce que je manque quelque chose, ou est-ce vraiment l'erreur de tutoriel? BTW, ajoutant mouvement ne résout pas mon premier problème.

Créé 07/11/2018 à 20:13
source utilisateur
Dans d'autres langues...                            


3 réponses

voix
2

Donc, quoi que ce soit avec un nom est une lvalue.

Une référence rvalue avec un nom est une lvalue.

Une référence rvalue se lie à rvalues, mais il est lui - même une lvalue.

Ainsi , xen ptr(x.ptr)est une référence rvalue, mais il a un nom, il est donc une lvalue.

Pour le traiter comme un rvalue, vous devez faire ptr( std::move(x).ptr ).

Bien sûr, cela est surtout inutile, comme un mouvement ptrne fait rien comme ptrun pointeur brut stupide.

Vous devriez suivre la règle de 0 ici.

class Example6 {
    std::unique_ptr<string> ptr;
  public:
    Example6 (string str) : ptr(std::make_unique<string>(std::move(str))) {cout << "DONT   MOVE " << '\n';}
    Example6():Example6("") {}
    ~Example6 () = default;
    // move constructor
    Example6 (Example6&& x) = default;
    // move assignment
    Example6& operator= (Example6&& x) = default;
    // access content:
    const string& content() const {
       if (!ptr) *this=Example6{};
       return *ptr;
    }
    // addition:
    Example6 operator+(const Example6& rhs) {
      return Example6(content()+rhs.content());
    }
};

parce que la logique métier et la gestion de la vie n'appartiennent pas entremêlées dans la même classe.

Bien que nous y sommes:

    // addition:
    Example6& operator+=(const Example6& rhs) & {
      if (!ptr) *this = Example6{};
      *ptr += rhs.content();
      return *this;
    }
    // addition:
    friend Example6 operator+(Example6 lhs, const Example6& rhs) {
      lhs += rhs;
      return lhs;
    }
Créé 07/11/2018 à 20:27
source utilisateur

voix
1

Copie constructeur est appelé ... - pourquoi?

La prémisse de votre question est défectueuse: Le constructeur de copie n'est pas appelé. En fait, la classe n'est pas copiable.

Le premier constructeur est un constructeur de convertir std::string. Le constructeur de conversion est appelé parce que les Example6objets sont initialisés avec un argument de chaîne. Une fois dans chacune de ces expressions:

  • Example6 foo("Exam")
  • Example6("ple")
  • Example6(content() + rhs.content()

... au lieu de constructeur de déplacement

Il y a quelques copies-initialisations par déplacement dans le programme. Cependant, ils peuvent tous être éludée par le compilateur.

Est-il possible d'éviter une certaine façon et toujours appeler constructeur déplacer?

Il y a quelques erreurs qui peuvent empêcher la copie élision. Par exemple, si vous avez écrit l'opérateur d'addition comme celui-ci:

return std::move(Example6(content()+rhs.content()));

Le compilateur ne parviendrait pas à elide le mouvement et probablement vous dire si vous avez de la chance:

warning: moving a temporary object prevents copy elision

Il ne devrait pas appeler

ptr(move(x.ptr))

au lieu de simplement

ptr(x.ptr)

Il n'y a pas besoin. Le déplacement d'un pointeur est exactement la même que la copie d'un pointeur. Même pour tous les types fondamentaux.

La façon dont je comprends cela, si nous utilisons la deuxième option, nous appelons constructeur de chaîne de copie, au lieu de se déplacer

ptrn'est pas une chaîne. Il est un pointeur sur une chaîne. La copie d'un pointeur ne fait rien à l'objet pointu.


PS. Le programme d'exemple est assez mauvaise qualité. Il ne devrait jamais être posséder des pointeurs nus en C ++.

Créé 07/11/2018 à 20:27
source utilisateur

voix
0

Je peux dire que votre classe n'a pas de constructeur de copie. Parce que le paramètre copie cteur doivent être const et référence

class Example6{ 
public:
    Example6(const Example6 &r);
};
Créé 07/11/2018 à 21:00
source utilisateur

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more