> For the complete documentation index, see [llms.txt](https://cours-cpp.gitbook.io/resources/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cours-cpp.gitbook.io/resources/abstractions/les-move-semantics.md).

# Les move & smart pointers

Les objets ne peuvent pas changer de possesseur : par exemple, si on souhaite passer un `std::vector<int>` d'un objet qui n'en a plus besoin à un autre, on est obligé de le copier, ce qui cause des problèmes de performance et ne correspond pas à ce qu'on souhaite faire. De plus, certains objets ne peuvent pas être copiés (comme les `std::thread`).

Pour pallier à ce problème, un nouveau type de référence a été introduit, qui connote un objet "consommable" : son contenu peut alors être déplacé, généralement pour créer un autre objet de même type. Il s'agit des *move references*, ou *rvalue references* (les références classiques étant appelées des *lvalue references*) :

```cpp
void use_string(std::string&& text);

int main() {
    // Cette string est temporaire, elle correspond donc à la move référence.
    use_string("hello");
    
    std::string text = "bouip";
    // Cette string n'est pas temporaire, elle ne peut donc pas être consommée.
    // use_string(text);
    // std::move va indiquer que l'objet n'est plus utilisé et peut être consommé.
    use_string(std::move(text));
}
```

Les objets peuvent être construits par *move* (`T::T(T&& moved)`) ou assignés par *move* (`T& operator=(T&& moved)`). Un objet pouvant être *move* doit, après avoir été *move*, avoir un destructeur valide (car son destructeur sera appelé) et ré-assignable.

Etant donné qu'un nouvel objet objet est créé, il ne sert à rien de *move* des objets qui ne transfèrent pas de ressources : *move* un `int` ou un `std::array<int, 12>` revient juste à copier l'objet.

Lorsqu'on créé une nouvelle classe, on va généralement chercher à respecter la [Rule of Five](https://en.wikipedia.org/wiki/Rule_of_three_\(C%2B%2B_programming\)), qui spécifie que si l'on implémente explicitement une des méthodes spéciales suivantes, alors on doit toutes les implémenter :

* Constructeur par copie
* Constructeur par move
* Affectation par copie
* Affectation par move
* Destructeur

### Les catégories des expressions

Les *lvalue references* (&) et *rvalue references* (&&) tiennent leurs noms des types des expressions auxquelles elles correspondent. Il existe 5 types d'expressions :

![](/files/-LCLNLGRbjR-iukOxdt9)

* Les *lvalues* désignent les objets non temporaires.
* Les *prvalues* désignent les objets temporaires (comme un objet retourné par une fonction). Il s'agit des expressions qui sont acceptées par les *rvalue references*.
* Les *xvalues* désignent les objets consommables (le x vient de 'expire').
* Les *glvalues* regroupent les *lvalues* et les *xvalues*. Il s'agit des expressions qui sont acceptées par les *lvalue references*.
* Les *rvalues* regroupent les *xvalues* et les *prvalues*. Il s'agit des objets qui peuvent être *move*.

Puisque l'on souhaite plutôt faire correspondre les *xvalues* aux *rvalue references*, on les change en *prvalues* grâce à `std::move`.

Les *const lvalue references* (`T const&`) acceptent également les temporaires, puisqu'elles ne modifient pas l'objet reçu. Cela rend le code suivant valide :

```cpp
MyClass value;

void make_value() {
    MyClass const var;
    // var est de type MyClass const&&.
    // L'affectation par move a comme signature :
    // MyClass& operator=(MyClass&&).
    // Comme la variable est const, c'est l'affectation par copie qui va être appelée :
    // MyClass& operator=(MyClass const&).
    value = std::move(var);
}
```

### std::unique\_ptr

Les constructeurs et affectations par *move* sont utilisés pour permettre à des objets (`std::string`, `std::vector`, ...) de gérer leurs ressources et de les passer à travers différents scopes. Des pointeurs utilisent ces opérations pour gérer l'objet pointé automatiquement : ils sont appelés *smart pointers*. La librairie standard met à disposition `std::unique_ptr`, `std::shared_ptr` et `std::weak_ptr`.

`std::unique_ptr` va gérer un objet (via un pointeur) en le détruisant dans son propre destructeur. Si on est l'unique propriétaire d'un objet, on va donc le stocker par valeur ou via un `std::unique_ptr`. Ce *smart pointer* ne fait rien d'autre et n'amène donc pas de surcoût par rapport à une gestion manuelle du pointeur.

Il s'agit du choix par défaut lorsque l'on veut stocker un objet qui n'est pas movable (il suffira de *move* le pointeur lui-même) ou un objet polymorphique (`std::unique_ptr` est covariant, et permet l'indirection pour stocker différents types sous la même appellation).

{% hint style="info" %}
`std::unique_ptr` est paramétrisé par le type du *function object* qui va être exécuté dans le destructeur du pointeur : `std::unique_ptr<T, Deleter = std::default_deleter<T>>`. Il est donc possible de faire utiliser un destructeur custom à un `std::unique_ptr`.
{% endhint %}

La fonction template `std::make_unique<T>` est préférée au constructeur de `std::unique_ptr<T>` [pour plusieurs avantages](https://stackoverflow.com/questions/22571202/differences-between-stdmake-unique-and-stdunique-ptr), dont certains ne sont plus valides en C++17. La déduction du type template de `std::unique_ptr` ne peut pas se faire via son constructeur, donc `std::make_unique` évite de mentionner deux fois le type.

```cpp
struct Base {};
struct Derived : Base {
    int val;
};

std::unique_ptr<Base> make_object(int val) {
    // Equivalent à :
    // return std::unique_ptr<Derived>(new Derived(val));
    return std::make_unique<Derived>(val);
}

struct Holder {
    std::unique_ptr<Base> obj;
}

int main() {
    Holder h = { make_object() };
    // std::unique_ptr est détruit, ce qui va supprimer l'objet.
}
```

### std::shared\_ptr et std::weak\_ptr

Si l'on n'est pas l'unique propriétaire d'un objet, `std::unique_ptr` ne suffit plus, car on souhaite détruire l'objet une fois que tous ses possesseurs sont détruits. `std::shared_ptr` remédie à ce problème en utilisant un compteur de référence thread-safe (des `std::shared_ptr` pointant sur le même objet peuvent être créés et détruits sur différents threads).

Par rapport à un `std::unique_ptr`, Il apporte un surcoût lors de sa création, de sa destruction et lors de la création du *control block* (stockant notamment le compteur de référence, via une allocation dynamique). De plus, il prend deux fois plus de place : il possède deux pointeurs, pour accéder à l'objet et au compteur de référence.

{% hint style="success" %}
`std::make_shared` comporte les mêmes avantages que `std::make_unique`, et apporte de meilleures performances comparé au constructeur de `std::shared_ptr` car il alloue l'objet et le *control block* en une fois.
{% endhint %}

Un `std::shared_ptr` peut être créé ou assigné depuis un `std::unique_ptr`, ce qui permet de créer des fonctions retournant des `std::unique_ptr` pour tous les usages. Contrairement à celui-ci, le destructeur de `std::shared_ptr` n'est pas stocké dans son type mais dans le *control block*.

`std::weak_ptr` est un pointeur ne prenant pas part au comptage de référence : il est donc possible qu'il pointe vers un objet déjà supprimé. Pour l'utiliser, on doit le changer en `std::shared_ptr` afin d'être sûr que l'objet ne se fera pas détruire pendant son utilisation. Il est utile notamment pour régler les problèmes de références cycliques.

```cpp
// Exemple tiré de cppreference.com.

std::weak_ptr<int> val;
 
void observe()
{
    std::cout << "use_count == " << val.use_count() << ": ";
    //Changement en std::shared_ptr
    if (auto spt = val.lock())
	    std::cout << *spt << "\n";
    else
        std::cout << "val is expired\n";
}
 
int main() {
    {
        auto ptr = std::make_shared<int>(42);
    	val = ptr;
        // use_count == 1, 42
    	observe();
    }
    // use_count == 0, val is expired
    observe();
}
```

###

### Les forwarding references

Lorsqu'un argument template est de type `T&&` avec `T` un paramètre template, l'argument n'est pas une *rvalue reference* (malgré son écriture) mais une *forwarding reference*. Cela permet de capturer des *glvalues* (auquel cas `T` sera déduit en `ValueType&`) et des *prvalues* (auquel cas `T` sera égal à `ValueType`).&#x20;

{% hint style="warning" %}
Il faut que T soit un paramètre template de la fonction, pour qu'il ne soit pas déduit autre part :

```cpp
// Exemple de forwarding reference.
template <class T>
void use_object(T&&);

template <class T>
struct Hello {
    // C'est une rvalue reference et non une forwarding reference.
    Hello(T&&);
}
```

{% endhint %}

Ces références sont généralement utilisées avec `std::forward<T>(t)`, qui va *move* la référence uniquement si elle désigne une *rvalue*. Sinon, la fonction renvoie simplement la référence.

```cpp
// Création d'un function object template.
template <class T>
struct constructor {
    // Son opérateur prend les références des arguments passés pour construire un T.
    // Les forwarding references vont différencier les objets pouvant être move de
    // ceux qui seront copiés, ce qui est fait avec std::forward.
    template <class...Args>
    T operator()(Args&&...args) const {
        // Attention : utiliser std::forward(args) déduit mal le type.
        return T{ std::forward<Args>(args) ... };
    }
};

struct Thing {
    float f;
    char c;
    int i;
};

Thing make_thing() {
    float pi = 3.14f;
    char c = 'u';
    // pi est copié, c est move, 42 est move.
    return constructor<Thing>{}(pi, std::move(c), 42);
}
```

Les *forwarding references* sont utiles mais peuvent prêter à confusion : est-ce que `T` désigne un `int` ? un `int&` ? un `int const&&` ? Il faut donc faire attention aux types manipulés afin d'éviter des copies inattendues, qui peuvent passer inaperçues (un type copiable ne fera pas mal fonctionner le programme, mais le fera ralentir).

### Pour aller plus loin

Avant C++11 (apportant les *move* et *les rvalue references*), les objets étaient *move* en implémentant une fonction `swap(T& lhs, T& rhs)`qui échange le contenu de deux objets. Cette pratique est encore utile pour des raisons d'*exception safety*.

Il ne faut pas *move* les types retournés par les fonctions en pensant optimiser la fonction (en évitant une copie) car la **Return Value Optimisation** (RVO, garantie en C++17) assure que l'objet, lorsqu'il est temporaire, n'est construit qu'une seule fois : il ne sera donc pas *move* ou copié. De plus, les compilateurs peuvent faire la même optimisation pour des variables non temporaires (**Named Return Value Optimisation**) et vont *move* l'objet si il ne peut pas être construit du côté de l'appelant de la fonction.

Attention au phénomène de [reference collapsing](https://stackoverflow.com/questions/13725747/concise-explanation-of-reference-collapsing-rules-requested-1-a-a-2) lors de la manipulation de types template.

[Des explication plus précises ](https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues)des catégories d'expressions existent.

Lorsqu'on souhaite accepter un objet qui peut être consommé ou copié, 3 alternatives existent :

* Dédoubler la fonction pour accepter l'objet via une *rvalue reference* ou une *lvalue reference*.
* Passer l'objet par valeur, puis le *move* dans la fonction. Cela coûte un *move* en plus par rapport à la première solution, mais évite de dédoubler la fonction.
* Rendre la fonction template pour recevoir l'objet avec une forwarding reference. On a les performances de la première solution sans dédoubler la fonction, mais on a un paramètre template (pouvant initialement accepter n'importe quel type).

[Les classes peuvent avoir des *ref-qualifiers*](https://akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/), ce qui contraindra la méthode à être appelée sur un temporaire ou un non-temporaire.

`std::shared_ptr` ne peut pas recevoir de destructeur custom via `std::make_shared`. Cependant, on peut utiliser `std::allocate_shared` pour quand même bénéficier des performances de `std::make_shared`.

### Challenges

(\*) Ecrire `force_move(value)` qui va cast l'objet en *prvalue* (comme `std::move`) ou lancer une erreur à la compilation si l'objet est *const*. Exemple :

```cpp
MyClass global;

void create_global_by_move() {
    MyClass v1;
    // Equivalent
    global = std::move(v1);
    global = force_move(v1);

    MyClass const v2;
    // Le constructeur par copie sera appelé.
    // force_move arrête la compilation pour éviter la confusion.
    global = std::move(v2);
    //global = force_move(v2);
}
```

(\*\*\*) Ecrire une classe template `unique_function<T(Args...)>` qui permet de manipuler n'importe quel *function object* pouvant être *move*, de manière similaire à `std::function<T(Args...)>`. Exemple :

```cpp
// Renvoie un générateur de nombres aléatoires.
// La lambda capture un std::unique_ptr, elle ne peut donc pas être copiée.
// std::function ne peut que stocker des fonction object pouvant être copiés.
std::unique_function<int(int, int)>
make_random_generator(std::unique_ptr<std::mt19937>&& producer) {
    return [producer = std::move(producer)] (int min, int max) {
        std::uniform_int_distribution dis(min, max);
        return dis(*producer);
    }
}
```

Bonus : ajouter à `unique_function` la *Small Buffer Optimisation* (SBO).
