jeudi 14 mars 2013

Reparlons de move semantics

Et voyons si cela change fortement la manière de passer ses paramètres en C++11. Imaginez que vous deviez implémenter une queue d'objets, par exemple pour la rendre thread safe. En c++03, l'on penserait immédiatement à écrire le "push" en passant son paramètre par référence constante, comme montré dans push1. Comparons donc cela avec push2, qui semble au premier abord moins optimal (notez que l'objet A affiche simplement quels sont ses constructeurs appelés):

class Queue
{
public:
  void push1(const A & a)
  {
    _deque.push_back(a);
  }

  void push2(A a)
  {
    _deque.push_back(std::move(a));
  }
  
private:
  std::deque<A> _deque;
};

Voyons ce que cela donne dans le cas général:

Queue q;
A a;
q.push1(a);

affiche

A::A()
A::A(const A &)

Alors que

Queue q;
A a;
q.push2(a);

affiche

A::A()
A::A(const A &)
A::A(const A &&)

C'est à peine pire si l'on part du principe qu'un constructeur move n'est pas cher. Mais maintenant, regardons dans le cas où le paramètre peut être déplacé:

Queue q;
q.push1(A());

affiche

A::A()
A::A(const A &)

ce qui n'est pas pire, mais

Queue q;  
q.push2(A());

affiche

A::A()
A::A(const A &&)

et là, c'est nettement mieux! L'on évite complètement la copie, et l'on déplace simplement l'objet jusqu'à la queue. Notez que

Queue q;
A a;
q.push2(std::move(a));

affiche

A::A()
A::A(const A &&)
A::A(const A &&)

En effet, le standard autorise le compilo à transformer un passage par copie en un move.

Je suis un tout petit peu ennuyé par cette nouvelle approche. En effet, la règle jusqu'ici était, passe par référence si tu peux, et par autre chose si tu dois. Et maintenant, il va falloir choisir entre un passage par référence et un passage par copie en fonction de ce que la fonction va faire avec le paramètre, ce qui me donne l'impression de casser l'encapsulation. Mais économiser des copies est plaisant.

Aucun commentaire: