web-dev-qa-db-fra.com

Alors, peut-on utiliser unique_ptr en toute sécurité dans des collections STL?

Je suis confondu avec une philosophie unique_ptr et Rvalue Move.

Disons que nous avons deux collections:

std::vector<std::auto_ptr<int>> autoCollection;
std::vector<std::unique_ptr<int>> uniqueCollection;

Maintenant, je m'attendrais à ce que ce qui suit échoue, car il n'ya pas de dire ce que l'algorithme se fait faire en interne et peut-être faire des copies pivotantes internes et autres, déchirant ainsi la propriété de l'auto_ptr:

std::sort(autoCollection.begin(), autoCollection.end());

J'ai compris. Et le compilateur interdit à juste titre cela se produire.

Mais alors je fais ça:

std::sort(uniqueCollection.begin(), uniqueCollection.end());

Et cela compile. Et je ne comprends pas pourquoi. Je n'ai pas pensé que SCRIM_PTRS pourrait être copié. Cela signifie-t-il qu'une valeur de pivot ne peut pas être prise, le tri est donc moins efficace? Ou est-ce que ce pivot est-il en fait un geste, qui est en fait aussi dangereux que la collection de auto_ptrs et doit être interdit par le compilateur?

Je pense que je manque d'informations cruciales, alors j'attends avec impatience quelqu'un pour me fournir l'AHA! moment.

44
DanDan

Je pense que c'est plus une question de philosophie que technique :)

La question sous-jacente est quelle est la différence entre le déplacement et la copie. Je ne saute pas dans la langue technique/normalista, le faisons simplement:

  • Copier: Créez un autre objet identique (ou au moins, celui qui devrait comparer égal)
  • Déplacer: prenez un objet et mettez-le à un autre endroit

Comme vous l'avez dit, il est possible de mettre en œuvre une étape de copie: Créez une copie dans le nouvel emplacement et jetez l'original. Cependant, il y a deux problèmes là-bas. On est de performance, la seconde concerne les objets utilisés pour Raii: lequel des deux devrait être propriétaire?

Un constructeur de déplacement approprié résout les 2 problèmes:

  • Il est clair que l'objet est propriétaire: le nouveau, puisque l'original sera jeté
  • Il est donc inutile de copier les ressources pointées vers, ce qui permet une plus grande efficacité

Les auto_ptr et unique_ptr sont une très bonne illustration de cela.

Avec un auto_ptr Vous avez une copie vissée sémantique: l'original et la copie ne comparent pas égal. Vous pouvez l'utiliser pour son déplacement sémantique mais il y a un risque que vous perdrez l'objet pointé quelque part.

D'autre part, le unique_ptr Est exactement que: il garantit un propriétaire unique de la ressource, évitant ainsi la copie et le problème de suppression inévitable qui suivrait. Et le non-copie est garanti au moment de la compilation. Par conséquent, il convient dans des conteneurs tant que vous n'essayez pas d'initialisation de la copie.

typedef std::unique_ptr<int> unique_t;
typedef std::vector< unique_t > vector_t;

vector_t vec1;                           // fine
vector_t vec2(5, unique_t(new Foo));     // Error (Copy)
vector_t vec3(vec1.begin(), vec1.end()); // Error (Copy)
vector_t vec3(make_move_iterator(vec1.begin()), make_move_iterator(vec1.end()));
    // Courtesy of sehe

std::sort(vec1.begin(), vec1.end()); // fine, because using Move Assignment Operator

std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2)); // Error (copy)

Donc, vous CAN Utilisez unique_ptr dans un conteneur (contrairement à auto_ptr), mais un certain nombre d'opérations seront impossibles parce qu'elles impliquent la copie que le type ne prend pas en charge.

Malheureusement, Visual Studio peut être assez lax dans l'application de la norme et dispose également d'un certain nombre d'extensions que vous devez désactiver pour assurer la transférabilité du code ... Ne l'utilisez pas pour vérifier la norme :)

53
Matthieu M.

Les unique_ptrs sont déplacés en utilisant leur constructeur de mouvement. unique_ptr est mobile, mais pas CopyConstructable.

Il y a un excellent article sur les références de rvalue ici . Si vous n'avez pas encore lu à leur sujet ou que vous êtes confus, jetez un coup d'œil!

13
rlbond

std::sort Cela pourrait fonctionner uniquement avec les opérations de déplacement et aucune copie, tant que la seule copie en direct de chaque objet à tout moment. Il s'agit d'une exigence plus faible que de travailler en place, car, en principe, vous pourriez attribuer un autre tableau temporairement et déplacer tous les objets en leur réorganisant.

par exemple avec std::vector<std::unique_ptr<T>> Dépassement de sa capacité, il alloue stockage pour un vecteur plus grand, puis déplace tous les objets de l'ancien stockage vers le nouveau. Ce n'est pas une opération en place mais c'est parfaitement valide.

Comme il s'avère, trier des algorithmes comme un tri rapide et une sorte de tas peut en fait fonctionner en place sans difficulté. La routine de partition de Quick-Tri utilise STD :: Swaper en interne, qui compte comme une opération de déplacement pour les deux objets impliqués. Lors de la sélection d'un pivot, une astuce consiste à l'échanger avec le premier élément de la plage, de cette façon, il ne sera jamais déplacé avant la fin de la partition.

7
yonil