7 votes

Existe-t-il un scénario utile pour déplacer des objets constants ?

J'ai réalisé que la connaissance commune que "vous ne pouvez pas déplacer un objet constant" n'est pas entièrement vrai. Vous pouvez, si vous déclarez le ctor de déplacement en tant que

X(const X&&);

Exemple complet ci-dessous :

#include <iostream>

struct X
{
    X() = default;
    X(const X&&) {std::cout << "const move\n";}
};

int main()
{
    const X x{};
    X y{std::move(x)};
}

Live on Coliru

Question : Y a-t-il une raison pour laquelle on voudrait une telle chose ? Un scénario utile/pratique ?

2voto

Lightness Races in Orbit Points 122793

Votre exemple ne fait rien bouger. Oui, vous avez écrit std::move pour obtenir une rvalue et vous avez invoqué un constructeur de déplacement, mais rien n'a été déplacé. Et ça ne peut pas, parce que l'objet est const .

Sauf si les membres qui vous intéressent sont marqués mutable vous ne seriez pas en mesure de vous déplacer. Il n'y a donc aucun scénario utile ou même possible.

2voto

Richard Hodges Points 1972

Je ne suis pas sûr que ce soit pratique, mais on peut le rendre légal à condition que les membres modifiés des données soient mutable .

Ce programme est légal, et si vous aimez ce genre de choses, il deviendrait facilement difficile à suivre :

#include <iostream>
#include <string>

struct animal
{
    animal(const animal&& other) : type(other.type) {
        other.type = "dog";
    }
    animal() = default;

    mutable std::string type = "cat";
};

std::ostream& operator<<(std::ostream& os, const animal& a)
{
    return os << "I am a " << a.type;
}
std::ostream& operator<<(std::ostream& os, const animal&& a)
{
    return os << "I am a " << a.type << " and I feel moved";
}

int main()
{
    const auto cat = animal();
    std::cout << cat << std::endl;

    auto dog = std::move(cat);
    std::cout << cat << std::endl;

    std::cout << dog << std::endl;
    std::cout << std::move(dog) << std::endl;
}

résultat attendu :

I am a cat
I am a dog
I am a cat
I am a cat and I feel moved

1voto

Nir Friedman Points 3165

Comme les commentaires l'ont noté, vous ne pouvez pas réellement "déplacer" quoi que ce soit hors de l'objet argument, parce qu'il est const (du moins, pas sans un cast const, qui est une mauvaise idée car il pourrait conduire à UB). Donc, il est clair que ce n'est pas utile pour le simple fait de déplacer. Le but de la sémantique de déplacement est de fournir une optimisation des performances, et ce n'est pas le cas ici, alors pourquoi le faire ?

Cela dit, je ne vois que deux cas où cela est utile. Le premier concerne les constructeurs "gourmands" :

#include <iostream>

struct Foo {
    Foo() = default;
    Foo(const Foo&) { std::cerr << "copy constructor"; }
    Foo(Foo&&) { std::cerr << "copy constructor"; }

    template <class T>
    Foo(T&&) { std::cerr << "forward"; }      
};

const Foo bar() { return Foo{}; }

int main() {
    Foo f2(bar());        
    return 0;   
}

Ce programme imprime "forward". La raison en est que le type déduit dans le modèle sera const Foo ce qui permet une meilleure adéquation. Cela se produit également lorsque vous avez des constructeurs variadiques à transmission parfaite. Commun pour les objets proxy. Bien sûr, retourner par une valeur constante est une mauvaise pratique, mais strictement parlant, ce n'est pas incorrect, et cela peut casser votre classe. Donc vous devriez vraiment fournir un Foo(const Foo&&) (qui délègue simplement au constructeur de copie) ; pensez-y comme si vous mettiez un point sur un t ou un point sur un i lorsque vous écrivez du code générique de haute qualité.

Le second cas se présente lorsque vous souhaitez supprimer explicitement les constructeurs de déplacement, ou un opérateur de conversion de déplacement :

struct Baz {
    Baz() = default;
    Baz(const Baz&) = default;
    Baz(Baz&&) = delete;
};

const Baz zwug() { return {}; }

int main() {
    Baz b2(zwug());
}

Ce programme se compile donc l'auteur a échoué dans sa mission. La raison en est que les surcharges de const ref correspondent à des const rvalues, et que la construction de const rvalue n'a pas été explicitement supprimée. Si vous voulez supprimer les déplacements, vous devrez également supprimer la surcharge const rvalue.

Le deuxième exemple peut sembler très obscur, mais disons que vous écrivez une classe qui fournit une vue d'une chaîne de caractères. Vous ne voudrez peut-être pas permettre qu'elle soit construite à partir d'une chaîne temporaire, car vous courrez un plus grand risque que la vue soit corrompue.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X