Безопасно ли поменять местами два разных вектора в C ++, используя метод std :: vector :: swap?

30

Предположим, что у вас есть следующий код:

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

Представьте, что вектора нет std::string, но классы:

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

Все еще безопасно поменять два вектора с std::vector::swapметодом: WidgetVector.swap(Widget2Vector);или это приведет к UB?

Эмануэле Оджано
источник

Ответы:

20

Это безопасно, потому что во время операции подкачки ничего не создается. Только члены данных класса std::vectorменяются местами.

Рассмотрим следующую демонстрационную программу, которая проясняет, как объекты класса std::vectorменяются местами.

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

Выход программы

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 

Как видите, только члены данных ptrи nпоменялись местами в функции swap. Дополнительные ресурсы не используются.

Аналогичный подход используется в классе std::vector.

Что касается этого примера

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

тогда есть объекты разных классов. Функция-член swap применяется к векторам одного типа.

Влад из Москвы
источник
6
Но что ППт в фактическом случае, когда векторы быть выгружены имеют разные классы?
Адриан Крот
4
@AdrianMole Функция-член swap, если она определена для данного типа вектора. Он не определен для векторов разных типов. Это не функция-член шаблона.
Влад из Москвы
«своп применяется к векторам одного типа». Вы должны добавить «только» между «есть» и «применяется».
СС Энн
Конечно, распределители состояний могут изменить ситуацию.
Дедупликатор
21

Да, это совершенно безопасно для обмена векторов одного типа.

Vector под капотом - это всего лишь несколько указателей, указывающих на данные, которые использует вектор, и «конец» последовательности. Когда вы вызываете swap, вы просто обмениваетесь указателями между векторами. Вам не нужно беспокоиться о том, что векторы имеют одинаковый размер из-за этого.

Векторы разных типов нельзя поменять местами swap. Вам нужно будет реализовать свою собственную функцию, которая выполняет преобразование и обмен.

NathanOliver
источник
3
Вам нужно внимательнее присмотреться ко второй части вопроса.
Марк Рэнсом
@MarkRansom Да. Пропустил 2. Обновлено.
Натан Оливер
13

Безопасно ли поменять местами два разных вектора в C ++, используя метод std :: vector :: swap?

Да. Обмен, как правило, можно считать безопасным. С другой стороны, безопасность субъективна и относительна и может рассматриваться с разных точек зрения. Таким образом, невозможно дать удовлетворительный ответ, не дополняя вопрос контекстом и не выбирая, какой вид безопасности рассматривается.

Все еще безопасно поменять местами два вектора с помощью метода std :: vector :: swap: WidgetVector.swap (Widget2Vector); или это приведет к UB?

Там не будет UB. Да, это все еще безопасно в том смысле, что программа плохо сформирована.

eerorika
источник
7

swapФункция определяется следующим образом : void swap( T& a, T& b );. Обратите внимание, что оба aи bдолжны быть) одного типа . (Для этой сигнатуры не определена такая функция: void swap( T1& a, T2& b )это не имеет смысла!)

Аналогично, swap()функция-член std::vectorкласса определяется следующим образом:

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

Теперь, поскольку не существует «эквивалентного» определения с переопределением шаблона (см. Явные специализации шаблонов функций ) для параметра функции (который будет иметь форму:) template <typename T2> void swap(std::vector<T2>& other), этот параметр должен быть вектором того же типа (шаблона), что и «вызывающий» класс (то есть он также должен быть vector<T1>).

Ваши std::vector<Widget>и std::vector<Widget2>два различных типа, так что вызов swapне будет компилировать, попробуйте ли использовать функцию - член либо объект (как ваш код делает), или с помощью специализации в std::swap()функции , которая принимает два std:vectorобъектов в качестве параметров.

Адриан Моул
источник
8
std::vector::swapтакое функция-член, как это может быть специализация автономной функции шаблона ???
Аконкагуа
1
Правильный результат, неправильные рассуждения.
Натан Оливер
2
@AdrianMole Хотя существует специализация для std::swap, это не то, что использует OP. Когда вы это делаете First.swap(Second);, вы вызываете std::vector::swapфункцию, отличную отstd::swap
NathanOliver
1
Извините, мой плохой ... Ваша ссылка ведет прямо к документации специализации std :: swap для векторов. Но это отличается от функции-члена , которая также существует и используется в вопросе (только).
Аконкагуа
2
Рассуждение на самом деле аналогично: элемент определяется как void swap(std::vector& other)(то есть std::vector<T>), а не как template <typename U> void swap(std::vector<U>& other)(при условии, что T является параметром типа для самого вектора).
Аконкагуа
4

Вы не можете менять векторы двух разных типов, но это ошибка компиляции вместо UB. vector::swapпринимает только векторы одного типа и распределителя.

Не уверен, что это сработает, но если вам нужен вектор, содержащий Widget2s, преобразованный из Widgets, вы можете попробовать это:

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

Widget2должно быть двигаться от конструктива Widget.

user233009
источник
0

using std::swap; swap(a, b);и a.swap(b);имеют точно такую ​​же семантику, где работает последняя; по крайней мере для любого вменяемого типа. Все стандартные типы в этом отношении нормальны.

Если вы не используете интересный распределитель (то есть с состоянием, не всегда равным и не распространяемый при замене контейнера, см. std::allocator_traits), Замена двух std::vectors с одинаковыми аргументами шаблона - это просто скучный обмен трех значений (для емкости, размера и данных). указатель). А перестановка основных типов, отсутствующих гонок данных, безопасна и не может быть выброшена.

Это даже гарантируется стандартом. См std::vector::swap().

Deduplicator
источник