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?

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.


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ę.


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.


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.


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.


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().

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.