Lors de l'implémentation de constructeurs de déplacement et d'opérateurs d'assignation de déplacement, on écrit souvent le code suivant:
p = other.p;
other.p = 0;
Les opérations de déplacement implicitement définies seraient implémentées avec le code suivant:
p = std::move(other.p);
Ce qui serait faux, car déplacer une variable de pointeur ne fait pas pas le mettre à null. Pourquoi donc? Existe-t-il des cas dans lesquels nous souhaiterions que les opérations de déplacement ne modifient pas la variable de pointeur d'origine?
Remarque: Par "se déplacer", je pas signifie simplement la sous-expression std::move(other.p)
, je veux dire toute l'expression p = std::move(other.p)
. Alors, pourquoi n'y a-t-il pas de règle de langage spéciale indiquant "Si le côté droit d'une affectation est un pointeur xvalue, il est défini sur null après que l'assignation a eu lieu"?
Définir un pointeur brut sur null après l'avoir déplacé implique que le pointeur représente la propriété. Cependant, beaucoup de pointeurs sont utilisés pour représenter les relations. De plus, il est recommandé pendant longtemps que les relations de propriété soient représentées différemment par rapport à l'utilisation d'un pointeur brut. Par exemple, la relation de propriété à laquelle vous faites référence est représentée par std::unique_ptr<T>
. Si vous souhaitez que les opérations de déplacement générées implicitement prennent en charge votre propriété, il vous suffit d'utiliser des membres qui représentent (et implémentent) le comportement de propriété souhaité.
De plus, le comportement des opérations de déplacement générées est cohérent avec ce qui a été fait avec les opérations de copie: elles ne font également aucune hypothèse de propriété et ne le font pas, par exemple. une copie complète si un pointeur est copié. Si vous voulez que cela se produise, vous devez également créer une classe appropriée codant la sémantique pertinente.
Moving rend l'objet déplacé "invalide". Il fait not le règle automatiquement sur un état "vide" sûr. Conformément au principe de longue date du C++ selon lequel "vous ne payez pas ce que vous n'utilisez pas", c'est votre travail si vous le souhaitez.
Je pense que la réponse est la suivante: appliquer un tel comportement vous-même est assez trivial et, par conséquent, la norme n'a pas jugé nécessaire d'imposer de règle au compilateur lui-même. Le langage C++ est énorme et tout ne peut pas être imaginé avant son utilisation. Prenons par exemple le modèle de C++. Il n’a pas été initialement conçu pour être utilisé de la manière dont il est utilisé aujourd’hui (c’est-à-dire sa capacité de métaprogrammation). Donc, je pense que la norme donne simplement la liberté, et n'a pas établi de règle spécifique pour std::move(other.p)
, suivant l'un des principes de conception: "Vous ne payez pas pour ce que vous n'utilisez pas" .
Bien que, std::unique_ptr
puisse être déplacé, mais pas copié. Donc, si vous voulez un pointeur-sémantique qui puisse être déplacé et copié, voici une implémentation triviale:
template<typename T>
struct movable_ptr
{
T *pointer;
movable_ptr(T *ptr=0) : pointer(ptr) {}
movable_ptr<T>& operator=(T *ptr) { pointer = ptr; return *this; }
movable_ptr(movable_ptr<T> && other)
{
pointer = other.pointer;
other.pointer = 0;
}
movable_ptr<T>& operator=(movable_ptr<T> && other)
{
pointer = other.pointer;
other.pointer = 0;
return *this;
}
T* operator->() const { return pointer; }
T& operator*() const { return *pointer; }
movable_ptr(movable_ptr<T> const & other) = default;
movable_ptr<T> & operator=(movable_ptr<T> const & other) = default;
};
Vous pouvez maintenant écrire des cours sans écrire votre propre sémantique de déplacement:
struct T
{
movable_ptr<A> aptr;
movable_ptr<B> bptr;
//...
//and now you could simply say
T(T&&) = default;
T& operator=(T&&) = default;
};
Notez que vous devez toujours écrire la sémantique de copie et le destructeur, car movable_ptr
est pas pointeur intelligent.
Par exemple, si vous avez un pointeur sur un objet partagé. N'oubliez pas que, après le déplacement d'un objet, il doit rester dans un état cohérent en interne, il est donc incorrect de définir un pointeur qui ne doit pas être null sur une valeur null.
C'est à dire.:
struct foo
{
bar* shared_factory; // This can be the same for several 'foo's
// and must never null.
};
Modifier
Voici une citation à propos de MoveConstructibe
du standard:
T u = rv;
...
rv’s state is unspecified [ Note:rv must still meet the requirements
of the library component that is using it. The operations listed in
those requirements must work as specified whether rv has been moved
from or not.