Czy bezpiecznie jest zamienić dwa różne wektory w C ++, używając metody std :: vector :: swap?

30

Załóżmy, że masz następujący kod:

#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;
}

Wyobraź sobie, że wektor nie jest std::stringjeszcze klasą:

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

Czy nadal można bezpiecznie zamienić dwa wektory za pomocą std::vector::swapmetody: WidgetVector.swap(Widget2Vector);czy doprowadzi to do UB?

Emanuele Oggiano
źródło

Odpowiedzi:

20

Jest bezpieczny, ponieważ nic nie jest tworzone podczas operacji wymiany. std::vectorWymieniani są tylko członkowie danych klasy .

Rozważ następujący program demonstracyjny, który wyjaśnia, w jaki sposób std::vectorzamieniane są obiekty klasy .

#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;
}

Wyjście programu to

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 

Jak widać tylko członków danych ptri nsą zamieniani w funkcji zamiany członka. Żadne dodatkowe zasoby nie są używane.

Podobne podejście stosuje się w klasie std::vector.

Jak w tym przykładzie

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

są też obiekty różnych klas. Zamiana funkcji składowej jest stosowana do wektorów tego samego typu.

Vlad z Moskwy
źródło
6
Ale co z faktycznym przypadkiem PO , w którym wymieniane wektory należą do różnych klas?
Adrian Mole,
4
@AdrianMole Zamiana funkcji składowej, jeśli została zdefiniowana dla danego typu wektora. Nie jest zdefiniowany dla wektorów różnych typów. To nie jest funkcja elementu szablonu.
Vlad z Moskwy
„zamiana jest stosowana do wektorów tego samego typu” Należy dodać „tylko” pomiędzy „jest” a „zastosowano”.
SS Anne,
Oczywiście stanowi dystrybutorzy mogą coś zmienić.
Deduplicator,
21

Tak, jest to całkowicie bezpieczne podczas wymiany wektorów tego samego typu.

Wektor pod maską to tylko kilka wskaźników wskazujących dane używane przez wektor i „koniec” sekwencji. Kiedy wywołujesz zamianę, po prostu wymieniasz te wskaźniki między wektorami. Z tego powodu nie musisz się martwić, że wektory są tego samego rozmiaru.

Wektory różnych typów nie mogą być zamieniane za pomocą swap. Musisz zaimplementować własną funkcję, która wykonuje konwersję i zamianę.

NathanOliver
źródło
3
Musisz przyjrzeć się bliżej drugiej części pytania.
Mark Ransom,
@MarkRansom Yep. Brakowało 2. Zaktualizowano
NathanOliver,
13

Czy bezpiecznie jest zamienić dwa różne wektory w C ++, używając metody std :: vector :: swap?

Tak. Zamiana może być ogólnie uważana za bezpieczną. Z drugiej strony bezpieczeństwo jest subiektywne i względne i można je rozpatrywać z różnych perspektyw. W związku z tym nie można udzielić satysfakcjonującej odpowiedzi bez rozszerzenia pytania o kontekst i wybrania rodzaju rozważanego bezpieczeństwa.

Czy nadal można bezpiecznie zamieniać dwa wektory za pomocą metody std :: vector :: swap: WidgetVector.swap (Widget2Vector); czy doprowadzi to do UB?

Nie będzie UB. Tak, nadal jest bezpieczny, ponieważ program jest źle sformułowany.

eerorika
źródło
7

swapFunkcja jest określona w następujący sposób: void swap( T& a, T& b );. Zauważmy, że oba ai bsą (i muszą być) ten sam typ . (Nie ma takiej funkcji zdefiniowanej z tym podpisem: void swap( T1& a, T2& b )ponieważ nie miałoby to sensu!)

Podobnie swap()funkcja std::vectorskładowa klasy jest zdefiniowana następująco:

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

Ponieważ nie istnieje „równoważna” definicja z nadpisaniem szablonu (patrz Jawna specjalizacja szablonów funkcji ) dla parametru funkcji (który miałby postać template <typename T2> void swap(std::vector<T2>& other):), parametr ten musi być wektorem tego samego typu (szablonu) co klasa „wzywająca” (tzn. musi to być również a vector<T1>).

Twój std::vector<Widget>i std::vector<Widget2>są dwa różne typy, więc wywołanie swapnie zostanie skompilowany, czy spróbować użyć funkcji składowej albo obiektu (jak kod robi), lub za pomocą specjalizację z std::swap()funkcji, która trwa dwa std:vectorobiekty jako parametry.

Adrian Mole
źródło
8
std::vector::swapjest funkcją składową, jak to może być specjalizacja wolnostojącej funkcji szablonu?
Aconcagua,
1
Właściwy wynik, błędne uzasadnienie.
NathanOliver,
2
@AdrianMole Chociaż istnieje specjalizacja std::swap, to nie jest to, czego używa OP. Kiedy to zrobisz First.swap(Second);, dzwonisz, std::vector::swapco jest inną funkcją niżstd::swap
NathanOliver,
1
Przepraszamy, mój zły ... Twój link prowadzi bezpośrednio do dokumentacji specjalizacji std :: swap dla wektorów. Ale różni się to od funkcji składowej , która również istnieje i jest używana w pytaniu (tylko).
Aconcagua,
2
Rozumowanie w rzeczywistości jest analogiczne: element jest zdefiniowany jako void swap(std::vector& other)(tj. std::vector<T>), A nie jako template <typename U> void swap(std::vector<U>& other)(zakładając, że T jest parametrem typu dla samego wektora).
Aconcagua,
4

Nie można zamienić wektorów dwóch różnych typów, ale jest to błąd kompilacji zamiast UB. vector::swapakceptuje tylko wektory tego samego typu i alokatora.

Nie jestem pewien, czy to zadziała, ale jeśli chcesz Widget2przekonwertować wektor zawierający s z Widgets, możesz spróbować:

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

Widget2będzie musiał być możliwy do przeniesienia Widget.

użytkownik233009
źródło
0

using std::swap; swap(a, b);i a.swap(b);mają dokładnie taką samą semantykę, w której działa ta druga; przynajmniej dla każdego rozsądnego typu. Wszystkie standardowe typy są pod tym względem rozsądne.

O ile nie użyjesz interesującego alokatora (co oznacza stanowe, nie zawsze równe i nie propagowane podczas wymiany kontenera, zobacz std::allocator_traits), zamiana dwóch std::vectors za pomocą tych samych szablonów-argumentów jest po prostu nudną zamianą trzech wartości (dla pojemności, wielkości i danych wskaźnik). A zamiana podstawowych typów, nieobecnych w wyścigach danych, jest bezpieczna i nie można jej rzucać.

Jest to nawet gwarantowane przez standard. Zobaczyć std::vector::swap().

Deduplikator
źródło