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'il std::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'autre std::mutex
devrait stocker le type mutex natif sur le tas afin qu'il reste à au même endroit lorsqu'il est déplacé entre des std::mutex
objets ou std::mutex
ne doit pas bouger. Le stocker sur le tas n'est pas possible, car a std::mutex
a un constexpr
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 utiliser new
. Donc, la seule option qui reste est std::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 aux std::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'un int
, vous pouvez copier sa valeur puis la mettre à zéro, mais c'est toujours un int
avec une valeur, ce ne sont que des octets de mémoire. Mais un int
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.