2395 votes

Qu'est-ce que la copie-et-swap idiome?

Qu'est-ce que cet idiome et quand doit-il être utilisé? Les problèmes qui permet-il de résoudre? L'idiome de changer lorsque le C++11 est-il utilisé?

Bien qu'il ait été mentionné dans de nombreux endroits, nous n'avons pas de singulier "qu'est-ce que" la question et la réponse, si elle est ici. Voici une liste partielle des lieux où il a été mentionné précédemment:

336voto

sbi Points 100828

La cession, à son coeur, est en deux étapes: la démolition de l'objet de l'ancien état et la construction de son nouvel état comme une copie d'un autre objet de l'état.

Fondamentalement, c'est ce que le destructeur et le constructeur de copie n', de sorte que la première idée serait de déléguer le travail à eux. Cependant, depuis la destruction de ne pas échouer, alors que la construction pourrait, en fait, nous voulons le faire dans l'autre sens: d'abord effectuer le rôle constructif et si cela réussi, puis de faire de la destruction de la partie. La copie-et-swap de langage est un moyen de faire exactement cela: Il appelle d'abord une catégorie " constructeur de copie pour créer un temporaire, puis swaps de ses données avec le temporaire, et puis laisse temporaire le destructeur de détruire l'ancien état.
Depuis swap() est censé ne jamais échouer, la seule partie qui peut échouer est la copie de la construction. C'est d'abord effectué, et si elle échoue, rien ne sera changé dans l'objet cible.

Dans sa forme raffinée, la copie et l'échange est mis en œuvre par avoir la copie effectuée par l'initialisation de la (non-référence) paramètre de l'opérateur d'affectation:

T& operator=(T tmp)
{
    this->swap(tmp);
    return *this;
}

54voto

Tony D Points 43962

Il y a quelques bonnes réponses déjà. Je vais me concentrer principalement sur ce que je pense qu'ils manquent de - une explication des "cons" avec la copie-et-swap idiome....

Qu'est-ce que la copie-et-swap idiome?

Mise en oeuvre de l'opérateur d'affectation en termes d'une fonction d'échange:

X& operator=(X rhs)
{
    swap(rhs);
    return *this;
}

L'idée fondamentale est que:

  • les plus sujettes à l'erreur le cadre de l'attribution d'un objet est d'assurer des ressources aux nouveaux besoins de l'état sont acquis (par exemple la mémoire, descripteurs)

  • cette acquisition peut être tentée avant la modification de l'état actuel de l'objet (c'est à dire *this) si une copie de la nouvelle valeur, qui est pourquoi rhs est acceptée par la valeur (c'est à dire copier) plutôt que par référence

  • la permutation de l'état de la copie locale rhs et *this est généralement relativement facile à faire sans échec potentiel/exceptions, compte tenu de la copie locale n'a pas besoin d'un état en particulier à la suite (faut juste adapter l'etat pour le destructeur à terme, comme pour un objet d'être déplacés de >= C++11)

Quand doit-il être utilisé? (Les problèmes qui permet-il de résoudre [/créer]?)

  • Lorsque vous souhaitez que le assignés à objecter pas affectée par une affectation qui lève une exception, en supposant que vous avez ou pouvez écrire un swap , avec de fortes exception de la garantie, et idéalement un qui ne peut pas échouer/throw..†

  • Lorsque vous voulez un endroit propre, facile à comprendre, robuste, de façon à définir l'opérateur d'affectation en termes de (plus simple) constructeur de copie, swap et destructeur fonctions.

    • L'auto-attribution de fait comme une copie-et-swap évite souvent négligé, des cas limites.‡

  • Lors de toute exécution de peine ou momentanément plus élevé de l'utilisation des ressources créées par avoir un autre objet temporaire lors de l'affectation n'est pas important pour votre application. ⁂

swap lancer: il est généralement possible d'échanger des données de manière fiable les membres que les objets de la piste par pointeur, mais non le pointeur de données des membres qui n'ont pas un lancer de libre échange, ou pour lequel la permutation doit être mise en œuvre en tant que X tmp = lhs; lhs = rhs; rhs = tmp; et la copie de la construction ou de la cession peut lever des, ont encore le potentiel de l'échec de la sortie de certains membres de données échangées et d'autres pas. Ce potentiel s'applique même à C++03 std::string's que James commentaires sur une autre réponse:

@wilhelmtell: En C++03, il n'y a aucune mention des exceptions potentiellement jetés par des std::string::swap (qui est appelée par std::swap). Dans C++0x, std::string::swap est noexcept et ne doit pas jeter des exceptions. – James McNellis Déc 22 '10 à 15:24


‡ l'opérateur d'assignation de mise en œuvre qui semble sain d'esprit lors de l'affectation d'un objet distincts peut facilement échouer pour de l'auto-attribution. Bien qu'il puisse sembler inimaginable que le code client serait même tentative d'auto-attribution, il peut arriver assez facilement au cours algo opérations sur les conteneurs, avec x = f(x); code f est (peut-être seulement pour quelques - #ifdef branches) une macro ala #define f(x) x ou une fonction retournant une référence à l' x, ou même (probablement inefficace mais concis) code comme celui - x = c1 ? x * 2 : c2 ? x / 2 : x;). Par exemple:

struct X
{
    T* p_;
    size_t size_;
    X& operator=(const X& rhs)
    {
        delete[] p_;  // OUCH!
        p_ = new T[size_ = rhs.size_];
        std::copy(p_, rhs.p_, rhs.p_ + rhs.size_);
    }
    ...
};

Sur l'auto-attribution, le code ci-dessus supprimer l' x.p_;, les points de p_ à un nouvellement allouée région de tas, puis tente de lire le non initialisée données qu'ils contiennent (Comportement Indéfini), si ce n'est pas faire quelque chose de trop étrange, copy des tentatives d'auto-attribution à chaque juste-destructed "T"!


⁂ La copie-et-swap idiome peut introduire de l'inefficacité ou des limitations en raison de l'utilisation d'un supplément temporaire (lorsque l'opérateur paramètre est exemplaire construit):

struct Client
{
    IP_Address ip_address_;
    int socket_;
    X(const X& rhs)
      : ip_address_(rhs.ip_address_), socket_(connect(rhs.ip_address_))
    { }
};

Ici, écrite à la main Client::operator= peut vérifier si *this est déjà connecté sur le même serveur que rhs (peut-être l'envoi d'un "reset" code si utile), alors que la copie-et-swap approche serait d'appeler le constructeur par copie qui serait susceptible d'être écrite pour ouvrir une nette prise de connexion puis fermez l'original. Non seulement pourrait-il que cela signifie d'un réseau à distance de l'interaction au lieu d'une simple variable de processus de copie, il pourrait aller à l'encontre du client ou du serveur de limites sur les sockets ou les connexions. (Bien sûr, cette classe a une assez horrible interface, mais c'est une autre affaire ;-P).

28voto

Oleksiy Points 3145

Cette réponse est plus comme un ajout et une légère modification pour les réponses ci-dessus.

Dans certaines versions de Visual Studio (et éventuellement d'autres compilateurs) il y a un bug qui est vraiment gênant et ne fait pas de sens. Donc, si vous déclarez/définir votre swap fonction comme ceci:

friend void swap(A& first, A& second) {

    std::swap(first.size, second.size);
    std::swap(first.arr, second.arr);

}

... le compilateur va hurler à vous lorsque vous appelez l' swap fonction de:

enter image description here

Cela a quelque chose à voir avec un friend fonction appelée et this objet passé en paramètre.


Un moyen de contourner cela est de ne pas utiliser friend de mots clés et de redéfinir l' swap fonction de:

void swap(A& other) {

    std::swap(size, other.size);
    std::swap(arr, other.arr);

}

Cette fois, vous pouvez les appeler swap et passez - other, faisant ainsi le bonheur du compilateur:

enter image description here


Après tout, vous n'avez pas besoin d'utiliser un friend fonction de swap de 2 objets. Il fait autant de sens pour s' swap une fonction membre qui a un other objet en tant que paramètre.

Vous avez déjà accès à l' this objet, afin de le transmettre en tant que paramètre est techniquement redondant.

21voto

Kerrek SB Points 194696

Je voudrais ajouter un mot d'avertissement lorsque vous traitez avec C++11-style allocateur-connaissance des conteneurs. L'échange et de cession sont légèrement différents de la sémantique.

Pour le concret, considérons un conteneur std::vector<T, A>A est certains allocateur dynamique de type, et nous allons comparer les fonctions suivantes:

void fs(std::vector<T, A> & a, std::vector<T, A> & b)
{ 
    a.swap(b);
    b.clear(); // not important what you do with b
}

void fm(std::vector<T, A> & a, std::vector<T, A> & b)
{
    a = std::move(b);
}

Le but de ces deux fonctions fs et fm est de donner de l' a l'état qu' b avaient au départ. Cependant, il y a une question cachée: Ce qui se passe si a.get_allocator() != b.get_allocator()? La réponse est: Ça dépend. Écrivons AT = std::allocator_traits<A>.

  • Si AT::propagate_on_container_move_assignment est std::true_type, alors fm réaffecte l'allocateur de a de la valeur de b.get_allocator(), sinon il n'a pas, et a continue d'utiliser son origine de l'allocateur. Dans ce cas, les éléments de données doivent être remplacées individuellement, depuis le stockage de l' a et b n'est pas compatible.

  • Si AT::propagate_on_container_swap est std::true_type, alors fs swaps à la fois des données et des allocateurs dans les attendus de la mode.

  • Si AT::propagate_on_container_swap est std::false_type, alors nous avons besoin d'un contrôle dynamique.

    • Si a.get_allocator() == b.get_allocator(), puis les deux contenants à usage de stockage compatibles, et d'échanger sur les produits de la manière habituelle.
    • Toutefois, si a.get_allocator() != b.get_allocator(), le programme a un comportement indéterminé (cf. [conteneur.les exigences.général/8].

Le résultat est que la permutation est devenu un non-trivial opération en C++11 dès que votre conteneur commence soutien dynamique allocateurs. C'est un peu "avancé" cas d'utilisation", mais il n'est pas totalement improbable, depuis déplacer optimisations habituellement ne devient intéressant que si votre classe gère une ressource, et de la mémoire est l'une des ressources les plus populaires.

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