Est-il sûr d'échanger deux vecteurs différents en C ++, en utilisant la méthode std :: vector :: swap?

30

Supposons que vous ayez le code suivant:

#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::vector<std::string> First{"example", "second" , "C++" , "Hello world" };
    std::vector<std::string> Second{"Hello"};

    First.swap(Second);

    for(auto a : Second) std::cout << a << "\n";
    return 0;
}

Imaginez que le vecteur ne le soit pas std::string, mais les classes:

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

Est-il toujours sûr d'échanger les deux vecteurs avec la std::vector::swapméthode: WidgetVector.swap(Widget2Vector);ou cela conduira-t-il à un UB?

Emanuele Oggiano
la source

Réponses:

20

Il est sûr car rien n'est créé pendant l'opération de swap. Seuls les membres de données de la classe std::vectorsont échangés.

Considérez le programme de démonstration suivant qui indique clairement comment les objets de la classe std::vectorsont échangés.

#include <iostream>
#include <utility>
#include <iterator>
#include <algorithm>
#include <numeric>

class A
{
public:
    explicit A( size_t n ) : ptr( new int[n]() ), n( n )
    {
        std::iota( ptr, ptr + n, 0 );   
    }

    ~A() 
    { 
        delete []ptr; 
    }

    void swap( A & a ) noexcept
    {
        std::swap( ptr, a.ptr );
        std::swap( n, a.n );
    }

    friend std::ostream & operator <<( std::ostream &os, const A &a )
    {
        std::copy( a.ptr, a.ptr + a.n, std::ostream_iterator<int>( os, " " ) );
        return os;
    }

private:    
    int *ptr;
    size_t n;
};

int main() 
{
    A a1( 10 );
    A a2( 5 );

    std::cout << a1 << '\n';
    std::cout << a2 << '\n';

    std::cout << '\n';

    a1.swap( a2 );

    std::cout << a1 << '\n';
    std::cout << a2 << '\n';

    std::cout << '\n';

    return 0;
}

La sortie du programme est

0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 

0 1 2 3 4 
0 1 2 3 4 5 6 7 8 9 

Comme vous ne voyez que les membres de données ptret nsont échangés dans l'échange de fonction membre. Aucune ressource supplémentaire n'est utilisée.

Une approche similaire est utilisée dans la classe std::vector.

Quant à cet exemple

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

puis il y a des objets de différentes classes. L'échange de fonction membre est appliqué aux vecteurs du même type.

Vlad de Moscou
la source
6
Mais qu'en est-il du cas réel du PO , où les vecteurs échangés sont de classes différentes ?
Adrian Mole
4
@AdrianMole Le swap de fonction membre s'il est défini pour le type donné de vecteur. Il n'est pas défini pour des vecteurs de types différents. Ce n'est pas une fonction membre de modèle.
Vlad de Moscou
"l'échange est appliqué aux vecteurs du même type" Vous devez ajouter un "seulement" entre "est" et "appliqué".
SS Anne
Bien sûr, les allocateurs avec état peuvent changer les choses.
Deduplicator
21

Oui, il est parfaitement sûr d'échanger des vecteurs du même type.

Le vecteur sous le capot n'est que quelques pointeurs pointant vers les données utilisées par le vecteur et la "fin" de la séquence. Lorsque vous appelez swap, vous échangez simplement ces pointeurs entre les vecteurs. Vous n'avez pas à vous inquiéter du fait que les vecteurs sont de la même taille à cause de cela.

Les vecteurs de types différents ne peuvent pas être échangés à l'aide de swap. Vous devez implémenter votre propre fonction qui effectue la conversion et l'échange.

NathanOliver
la source
3
Vous devez examiner de plus près la deuxième partie de la question.
Mark Ransom
@MarkRansom Yep. Manqué le 2. Mise à jour.
NathanOliver
13

Est-il sûr d'échanger deux vecteurs différents en C ++, en utilisant la méthode std :: vector :: swap?

Oui. L'échange peut généralement être considéré comme sûr. D'un autre côté, la sécurité est subjective et relative et peut être considérée sous différents angles. En tant que tel, il n'est pas possible de donner une réponse satisfaisante sans augmenter la question avec un contexte et sans choisir le type de sécurité envisagé.

Est-il toujours sûr d'échanger les deux vecteurs avec la méthode std :: vector :: swap: WidgetVector.swap (Widget2Vector); ou cela conduira à un UB?

Il n'y aura pas d'UB. Oui, il est toujours sûr dans le sens où le programme est mal formé.

eerorika
la source
7

La swapfonction est définie comme suit: void swap( T& a, T& b );. Notez ici que les deux aet bsont (et doivent être) du même type . (Il n'y a pas une telle fonction définie avec cette signature:, void swap( T1& a, T2& b )car cela n'aurait aucun sens!)

De même, la swap()fonction membre de la std::vectorclasse est définie comme suit:

template<class T1> class vector // Note: simplified from the ACTUAL STL definition
{
//...
public:
    void swap( vector& other );
//...
};

Maintenant, comme il n'y a pas de définition «équivalente» avec un remplacement de modèle (voir Spécialisations explicites de modèles de fonction ) pour le paramètre de fonction (qui serait de la forme:) template <typename T2> void swap(std::vector<T2>& other), ce paramètre doit être un vecteur du même type (modèle) que la classe «appelante» (c'est-à-dire qu'elle doit également être a vector<T1>).

Votre std::vector<Widget>et std::vector<Widget2>sont deux types différents , donc l'appel à swapne sera pas compilé, que vous essayiez d'utiliser la fonction membre de l'un ou l'autre objet (comme le fait votre code), ou que vous utilisiez la spécialisation de la std::swap()fonction qui prend deux std:vectorobjets comme paramètres.

Adrian Mole
la source
8
std::vector::swapest une fonction membre, comment cela peut-il être une spécialisation d'une fonction de modèle autonome ???
Aconcagua
1
Bon résultat, mauvais raisonnement.
NathanOliver
2
@AdrianMole Bien qu'il existe une spécialisation pour std::swap, ce n'est pas ce que l'OP utilise. Lorsque vous le faites First.swap(Second);, vous appelez std::vector::swapce qui est une fonction différente destd::swap
NathanOliver
1
Désolé, ma mauvaise ... Votre lien va directement à la documentation de la spécialisation std :: swap pour les vecteurs. Mais c'est différent de la fonction membre , qui existe également et qui est utilisée en question (uniquement).
Aconcagua
2
Le raisonnement est en fait analogue: le membre est défini comme void swap(std::vector& other)(c'est-à-dire std::vector<T>), pas comme template <typename U> void swap(std::vector<U>& other)(en supposant que T soit le paramètre de type pour le vecteur lui-même).
Aconcagua
4

Vous ne pouvez pas échanger des vecteurs de deux types différents mais c'est une erreur de compilation au lieu de UB. vector::swapaccepte uniquement les vecteurs du même type et allocateur.

Je ne sais pas si cela fonctionnera, mais si vous voulez un vecteur contenant Widget2s converti à partir de Widgets, vous pouvez essayer ceci:

std::vector<Widget2> Widget2Vector(
    std::make_move_iterator(WidgetVector.begin()),
    std::make_move_iterator(WidgetVector.end())
);

Widget2devra être déplacer constructible de Widget.

user233009
la source
0

using std::swap; swap(a, b);et a.swap(b);ont exactement la même sémantique où celle-ci fonctionne; au moins pour tout type sain d'esprit. Tous les types standard sont sains à cet égard.

Sauf si vous utilisez un allocateur intéressant (c'est-à-dire avec état, pas toujours égal et non propagé sur l'échange de conteneurs, voir std::allocator_traits), l'échange de deux std::vectors avec les mêmes arguments de modèle n'est qu'un échange ennuyeux de trois valeurs (pour la capacité, la taille et les données) aiguille). Et l'échange de types de base, les courses de données absentes, est sûr et ne peut pas être lancé.

Cela est même garanti par la norme. Tu vois std::vector::swap().

Déduplicateur
la source