# 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).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://cours-cpp.gitbook.io/resources/abstractions/les-move-semantics.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
