É seguro trocar dois vetores diferentes em C ++, usando o método std :: vector :: swap?

30

Suponha que você tenha o seguinte código:

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

Imagine que o vetor ainda não std::stringé uma classe:

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

Ainda é seguro trocar os dois vetores pelo std::vector::swapmétodo: WidgetVector.swap(Widget2Vector);ou isso levará a um UB?

Emanuele Oggiano
fonte

Respostas:

20

É seguro porque nada é criado durante a operação de troca. Somente membros de dados da classe std::vectorsão trocados.

Considere o seguinte programa demonstrativo que deixa claro como os objetos da classe std::vectorsão trocados.

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

A saída do programa é

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 

Como você vê apenas membros de dados ptre né trocado na troca de função de membro. Nenhum recurso adicional é usado.

Uma abordagem semelhante é usada na classe std::vector .

Quanto a este exemplo

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

depois, existem objetos de diferentes classes. A troca da função de membro é aplicada a vetores do mesmo tipo.

Vlad de Moscou
fonte
6
Mas e o caso real do OP , onde os vetores trocados são de classes diferentes ?
Adrian Mole
4
@AdrianMole A função de membro swap, se definida para o tipo especificado do vetor. Não está definido para vetores de tipos diferentes. Não é uma função de membro do modelo.
Vlad de Moscow
"swap é aplicado a vetores do mesmo tipo" Você deve adicionar um "somente" entre "é" e "aplicado".
SS Anne
Obviamente, alocadores com estado podem mudar as coisas.
Deduplicator
21

Sim, é perfeitamente seguro trocar vetores do mesmo tipo.

O vetor sob o capô é apenas alguns ponteiros apontando para os dados que o vetor usa e o "fim" da sequência. Quando você chama swap, basta trocar esses ponteiros entre os vetores. Você não precisa se preocupar que os vetores tenham o mesmo tamanho por causa disso.

Vetores de tipos diferentes não podem ser trocados usando swap. Você precisaria implementar sua própria função que faz a conversão e troca.

NathanOliver
fonte
3
Você precisa olhar mais de perto a segunda parte da pergunta.
Mark Ransom
@MarkRansom Yep. Perdeu o 2. Atualizada.
NathanOliver
13

É seguro trocar dois vetores diferentes em C ++, usando o método std :: vector :: swap?

Sim. A troca geralmente pode ser considerada segura. Por outro lado, a segurança é subjetiva e relativa e pode ser considerada sob diferentes perspectivas. Como tal, não é possível dar uma resposta satisfatória sem aumentar a pergunta com um contexto e escolher que tipo de segurança está sendo considerada.

Ainda é seguro trocar os dois vetores com o método std :: vector :: swap: WidgetVector.swap (Widget2Vector); ou isso levará a um UB?

Não haverá UB. Sim, ainda é seguro no sentido de que o programa está mal formado.

eerorika
fonte
7

A swapfunção é definida como se segue: void swap( T& a, T& b );. Observe aqui que ambos ae bsão (e devem ser) o mesmo tipo . (Não existe essa função definida com esta assinatura:void swap( T1& a, T2& b ) como não faria sentido!)

Da mesma forma, a swap()função de membro da std::vectorclasse é definida da seguinte maneira:

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

Agora, como não há definição 'equivalente' com uma substituição de modelo (consulte Especializações explícitas de modelos de função ) para o parâmetro de função (que seria do formato template <typename T2> void swap(std::vector<T2>& other):), esse parâmetro deve ser um vetor do mesmo tipo (modelo) que a classe 'chamando' (ou seja, também deve ser a vector<T1>).

Você std::vector<Widget>e std::vector<Widget2>são dois tipos diferentes , portanto, a chamada para swapnão será compilada, se você tentar usar a função de membro de qualquer objeto (como o seu código faz) ou usar a especialização da std::swap()função que aceita dois std:vectorobjetos como parâmetros.

Adrian Mole
fonte
8
std::vector::swapé uma função membro, como isso pode ser uma especialização de uma função de modelo independente ???
Aconcagua
11
Resultado certo, raciocínio errado.
NathanOliver
2
@AdrianMole Embora exista uma especialização para std::swap, não é isso que o OP está usando. Quando você o faz First.swap(Second);, você chama std::vector::swapqual é uma função diferente destd::swap
NathanOliver
11
Desculpe, meu mal ... Seu link vai direto para a documentação da especialização std :: swap para vetores. Mas isso é diferente da função de membro , que também existe e é usada em questão (apenas).
Aconcagua
2
O raciocínio na verdade é análogo: o membro é definido como void swap(std::vector& other)(ie std::vector<T>), não como template <typename U> void swap(std::vector<U>& other)(assumindo T sendo o parâmetro do tipo para o próprio vetor).
Aconcagua
4

Você não pode trocar vetores de dois tipos diferentes, mas é um erro de compilação em vez de UB. vector::swapaceita apenas vetores do mesmo tipo e alocador.

Não tenho certeza se isso funcionará, mas se você deseja que um vetor contenha Widget2s convertido de Widgets, tente:

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

Widget2terá que ser movido a partir de Widget.

user233009
fonte
0

using std::swap; swap(a, b);e a.swap(b);tem exatamente a mesma semântica em que esta funciona; pelo menos para qualquer tipo são. Todos os tipos padrão são sensatos a esse respeito.

A menos que você use um alocador interessante (ou seja, com estado, nem sempre igual e não propagado na troca de contêiner, consulte std::allocator_traits), trocando duasstd::vector s com os mesmos argumentos de modelo é apenas uma troca chata de três valores (capacidade, tamanho e dados) ponteiro). E trocar tipos básicos, ausência de corridas de dados, é seguro e não pode ser descartado.

Isso é garantido pelo padrão. Veja std::vector::swap().

Desduplicador
fonte