J'ai été surpris que cela n'apparaisse pas dans mes résultats de recherche, je pensais que quelqu'un l'aurait déjà demandé, étant donné l'utilité de la sémantique de déplacement en C ++ 11:
Quand dois-je (ou est-ce une bonne idée pour moi de) rendre une classe non déplaçable en C ++ 11?
(Raisons autres que des problèmes de compatibilité avec le code existant, c'est-à-dire.)
c++
c++11
move-semantics
c++-faq
user541686
la source
la source
+1
de ma part) avec une réponse très approfondie de Herb (ou de son jumeau, comme il semble ), alors j'en ai fait une entrée de FAQ. Si quelqu'un s'oppose à me cingler dans le salon , cela peut être discuté là-bas.T x = std::move(anotherT);
être légal" ne sont pas équivalents. Ce dernier est une demande de mouvement qui pourrait se rabattre sur le cteur de copie dans le cas où T n'a pas de cteur de mouvement. Alors, que signifie exactement «mobile»?Réponses:
La réponse de Herb (avant modification) a fait un bon exemple d'un type qui ne doit pas être mobile:
std::mutex
.Le type mutex natif du système d'exploitation (par exemple
pthread_mutex_t
sur les plates-formes POSIX) peut ne pas être "invariant d'emplacement", ce qui signifie que l'adresse de l'objet fait partie de sa valeur. Par exemple, le système d'exploitation peut conserver une liste de pointeurs vers tous les objets mutex initialisés. S'ilstd::mutex
contenait un type mutex du système d'exploitation natif en tant que membre de données et que l'adresse du type natif doit rester fixe (car le système d'exploitation maintient une liste de pointeurs vers ses mutex), l'un ou l'autrestd::mutex
devrait stocker le type mutex natif sur le tas afin qu'il reste à au même endroit lorsqu'il est déplacé entre desstd::mutex
objets oustd::mutex
ne doit pas bouger. Le stocker sur le tas n'est pas possible, car astd::mutex
a unconstexpr
constructeur et doit être éligible pour une initialisation constante (ie initialisation statique) de sorte qu'un globalstd::mutex
est garanti pour être construit avant le début de l'exécution du programme, donc son constructeur ne peut pas utilisernew
. Donc, la seule option qui reste eststd::mutex
d'être inamovible.Le même raisonnement s'applique aux autres types qui contiennent quelque chose qui nécessite une adresse fixe. Si l'adresse de la ressource doit rester fixe, ne la déplacez pas!
Il y a un autre argument pour ne pas bouger,
std::mutex
c'est qu'il serait très difficile de le faire en toute sécurité, car vous auriez besoin de savoir que personne n'essaye de verrouiller le mutex au moment où il est déplacé. Étant donné que les mutex sont l'un des éléments de base que vous pouvez utiliser pour empêcher les courses de données, il serait malheureux qu'ils ne soient pas en sécurité contre les races elles-mêmes! Avec un immeuble,std::mutex
vous savez que la seule chose que tout le monde peut faire une fois qu'il a été construit et avant qu'il ne soit détruit est de le verrouiller et de le déverrouiller, et ces opérations sont explicitement garanties pour être thread-safe et ne pas introduire de courses de données. Ce même argument s'applique auxstd::atomic<T>
objets: à moins qu'ils ne puissent être déplacés de manière atomique, il ne serait pas possible de les déplacer en toute sécurité, un autre thread pourrait essayer d'appelercompare_exchange_strong
sur l'objet juste au moment où il est déplacé. Donc, un autre cas où les types ne devraient pas être déplaçables est celui où ils sont des blocs de construction de bas niveau d'un code concurrent sûr et doivent garantir l'atomicité de toutes les opérations sur eux. Si la valeur de l'objet peut être déplacée vers un nouvel objet à tout moment, vous devez utiliser une variable atomique pour protéger chaque variable atomique afin que vous sachiez s'il est sûr de l'utiliser ou si elle a été déplacée ... et une variable atomique à protéger cette variable atomique, et ainsi de suite ...Je pense que je généraliserais pour dire que lorsqu'un objet n'est qu'un pur morceau de mémoire, pas un type qui agit comme un support pour une valeur ou une abstraction d'une valeur, cela n'a pas de sens de le déplacer. Les types fondamentaux tels que
int
ne peuvent pas bouger: les déplacer n'est qu'une copie. Vous ne pouvez pas extraire les tripes d'unint
, vous pouvez copier sa valeur puis la mettre à zéro, mais c'est toujours unint
avec une valeur, ce ne sont que des octets de mémoire. Mais unint
est toujours mobiledans les termes de la langue car une copie est une opération de déplacement valide. Cependant, pour les types non copiables, si vous ne voulez pas ou ne pouvez pas déplacer la partie de mémoire et que vous ne pouvez pas non plus copier sa valeur, alors elle n'est pas déplaçable. Un mutex ou une variable atomique est un emplacement spécifique de la mémoire (traité avec des propriétés spéciales) donc n'a pas de sens de se déplacer, et n'est pas non plus copiable, donc il n'est pas déplaçable.la source
Réponse courte: si un type est copiable, il doit également être déplaçable. Cependant, l'inverse n'est pas vrai: certains types comme
std::unique_ptr
sont déplaçables mais cela n'a pas de sens de les copier; ce sont naturellement des types de mouvement uniquement.Une réponse légèrement plus longue suit ...
Il existe deux types principaux de types (parmi d'autres plus spécifiques tels que les traits):
Les types de type valeur, tels que
int
ouvector<widget>
. Celles-ci représentent des valeurs et devraient naturellement être copiables. En C ++ 11, vous devriez généralement considérer le déplacement comme une optimisation de la copie, et donc tous les types copiables doivent naturellement être déplaçables ... le déplacement est juste un moyen efficace de faire une copie dans le cas souvent courant où vous ne le faites pas. Vous n'avez plus besoin de l'objet original et vous allez le détruire de toute façon.Types de type référence qui existent dans les hiérarchies d'héritage, tels que les classes de base et les classes avec des fonctions membres virtuelles ou protégées. Ceux-ci sont normalement détenus par un pointeur ou une référence, souvent un
base*
oubase&
, et ne fournissent donc pas de construction de copie pour éviter le découpage; si vous voulez obtenir un autre objet comme un objet existant, vous appelez généralement une fonction virtuelle commeclone
. Ceux-ci n'ont pas besoin de construction ou d'affectation de déplacement pour deux raisons: ils ne sont pas copiables, et ils ont déjà une opération de «déplacement» naturelle encore plus efficace - il vous suffit de copier / déplacer le pointeur vers l'objet et l'objet lui-même ne le fait pas doivent passer du tout à un nouvel emplacement de mémoire.La plupart des types appartiennent à l'une de ces deux catégories, mais il existe également d'autres types de types qui sont également utiles, mais plus rares. En particulier ici, les types qui expriment la propriété unique d'une ressource, tels que
std::unique_ptr
, sont naturellement des types à déplacement uniquement, car ils ne sont pas de type valeur (cela n'a pas de sens de les copier) mais vous les utilisez directement (pas toujours par pointeur ou référence) et souhaitent ainsi déplacer des objets de ce type d'un endroit à un autre.la source
std::mutex
c'était immobile, car les mutex POSIX sont utilisés par adresse.En fait, lorsque je cherche autour de moi, j'ai trouvé que certains types de C ++ 11 ne sont pas déplaçables:
mutex
types (recursive_mutex
,timed_mutex
,recursive_timed_mutex
,condition_variable
type_info
error_category
locale::facet
random_device
seed_seq
ios_base
basic_istream<charT,traits>::sentry
basic_ostream<charT,traits>::sentry
atomic
typesonce_flag
Apparemment, il y a une discussion sur Clang: https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c++/pCO1Qqb3Xa4
la source
iterators / iterator adaptors
devrait être édité car C ++ 11 a move_iterator?std::reference_wrapper
. Ok, les autres semblent en effet immobiles.ios_base
,type_info
,facet
), 3. trucs bizarre assortiment (sentry
). Les seules classes immuables qu'un programmeur moyen écrira sont probablement dans la deuxième catégorie.Une autre raison que j'ai trouvée - la performance. Disons que vous avez une classe «a» qui détient une valeur. Vous souhaitez générer une interface qui permet à un utilisateur de modifier la valeur pendant une durée limitée (pour une étendue).
Un moyen d'y parvenir est de renvoyer un objet 'scope guard' à partir de 'a' qui remet la valeur dans son destructeur, comme ceci:
Si je rendais change_value_guard mobile, je devrais ajouter un `` si '' à son destructeur qui vérifierait si la garde a été déplacée - c'est un si supplémentaire et un impact sur les performances.
Oui, bien sûr, il peut probablement être optimisé par n'importe quel optimiseur sensé, mais c'est toujours bien que le langage (cela nécessite C ++ 17 cependant, pour pouvoir retourner un type non mobile nécessite une élision de copie garantie) ne nous oblige pas payer cela si nous n'allons pas déplacer la garde de toute façon autre que de la renvoyer de la fonction de création (le principe de ne pas payer pour ce que vous n'utilisez pas).
la source