Mémoire

Déplacement d’objet avec std::move

struct A {
    // Constructeur
    A() { /* ... */ }

Dessin_08-04-2022_16.48.07.excalidraw

    // Constructeur de recopie
    A(const A &s) { /* ... */ }

    // Opérateur d'assignation
    A& operator=(const A& s) { /* ... */ }

operateur

    // Destructeur
    ~A() { /* ... */ }
};

destruction

#include <utility> // pour std::move(/* ... */);

int main(void) {
    vector<std::string> v;
    std::string s;

    while(s != "END") {
        std::cin >> s;
        // v.push_back(s);
        v.push_back(std::move(s));
    }
}

main

On peut utiliser move !

Autre exemple :

std::string a = "Bonjour";
std::string b = "Machin";
std::string c = "truc";

a = b;
// a contient "Machin"
// b contient "Machin"

c = std::move(b);
// c contient "Machin"
// b contient la chaîne vide

std::move a un interêt quand il faut déplacer une ressource sans tout copier.

Pour pouvoir l’utiliser dans notre classe, il faut implanter un autre constructeur de recopie ainsi qu’un autre constructeur d’assignation : (appellé respectivement constructeur de déplacement et l’opérateur de déplacement)

A(const A&& s) { /* ... */}
A& operator=(const A&& s) { /* ... */}

(on double la référence)

Conclusion

Afin de pouvoir optimiser certaines instructions sur une classe A. On peut implanter le constructeur de déplacement (A(const A&&)) et l’assignation par déplacement (A& operator=(const A&&)) que l’on utilisera avec std::move() de <utility>.

Exemple d’implantation
On suppose que
- on a un pointeur vers un int qui va pointer vers une zone mémoire que l’on va allouer dynamiquement)
- on a une classe Mere déjà définit

#include <utility>

using namespace std;

struct Fille: public Mere {
     int * p = NULL;

    // Constructeur qui ne fait rien
    Fille(void) { }

    // Constructeur qui alloue de la mémoire
    Fille(int a): Mere(a) {
        p = malloc(sizeof(*p) * 100); // il va falloir free cette allocation
                                      // lors de la déstruction
        for(int i = 0; i < 100; i++) {
            p[i] = i;
        }
    }

    // Destructeur qui désalloue la mémoire alloué lors de la construction
    ~Fille(): ~Mere() {
        if(p != NULL) {
            free(p);
        }
    }

    // Constructeur de copie
    Fille(const Fille& s): Mere(s) {
        if(s.p != NULL) { // Dans ce cas là on fait une allocation
            p = malloc(sizeof(*p) * 100);
            for(int i = 0; i < 100; i++) {
                p[i] = s.p[i];
            }
        }
    }

    // Affectation/Assignation
    Fille& operator=(const Fille& s) {
        (*this).Mere::operator(s); // on appelle celui de la classe Mere
        if(s.p == NULL) { // si s.p est NULL
            if(p != NULL) { // mais que p l'est pas, on libère p
                free(p);
                p = NULL;
            }
        } else { // sinon si s.p n'est pas NULL
            if(p == NULL) { // on alloue la mémoire si besoin
                p = malloc(sizeof(*p) * 100);
            }
            for(int i = 0; i < 100; i++) { // on recopie les valeurs
                p[i] = s.p[i];
            }
        }

        return *this;
    }

    // Constructeur de déplacement
    Fille(const Fille&& s): Mere(move(s)) {
        p = s.p; // déplacement
        s.p = NULL; // ancienne emplacement est désormais NULL
    }

    // Affectation de déplacement
    Fille& operator=(const Fille&& s) {
        // Partie mère
        (*this).Mere::operator=(move(s));

        // Partie Fille
        if(p != NULL) {
            free(p);
        }
        p = s.p;
        s.p = NULL;

        return *this;
    }
};

Appel du constructeur d’une classe sans réservation de mémoire

Allocation de mémoire, puis appel du constructeur d’une classe A :

A a;
A * p = new A;

Pour appeller un constructeur d’une classe A sur une adresse addr (sans allocation de mémoire) on peut utiliser la syntaxe :

new(addr) A{ /* ... */ }; // appel du constructeur `A` à l'adresse `addr`

Exemple

void * ptr = malloc(sizeof(*ptr)); // réservation de la mémoire

A * c = new(ptr) A{ /* ... */ }; // appel au constructeur sans réservation mémoire

// ...

(*c).~A(); // appel au destructeur sans libération de la mémoire alloué plus haut
free(ptr);

Utile lorsque l’on ne veut réallouer plusieurs fois de la mémoire.

Cast dynamique

En C++, il y a plusieurs moyen de faire des conversions.
En C : (type)expression;
En C++ :

Exemple dynamic_cast<>()

Mere * p = new Fille( /* ... */);
Fille * c = dynamic_cast<Fille>(p); // au moment de l'execution, NULL si ce n'est
                                    // pas une fille ou un type hérité de fille
if(c == NULL) {
    cout << "N'est pas une fille" << endl;
} else {
    cout << "Est une fille" << endl;
}
// Ce code affichera toujours "Est une fille"