Чи безпечно поміняти два різні вектори на C ++, використовуючи метод swap std :: vector ::?


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);або це призведе до розвитку УБ?

Відповіді:


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;

то є об’єкти різних класів. Зміна функції члена застосовується для однотипних векторів.


6
Але що з дійсного випадку ОП , коли вектори, що змінюються, мають різні класи?
Адріан Мол

4
@AdrianMole Функція-член swap, якщо визначена для даного типу вектора. Він не визначений для векторів різних типів. Це не функція члена шаблону.
Влад з Москви

"swap застосовується до однотипних векторів" Ви повинні додати "тільки" між "є" і "застосовано".
СС Енн

Звичайно, державні розподільники можуть змінити щось.
Deduplicator

21

Так, це цілком безпечно для заміни векторів одного типу.

Вектор під кришкою - це лише кілька покажчиків, що вказують на дані, які вектор використовує та "кінець" послідовності. Коли ви викликаєте своп, ви просто обмінюєте ці покажчики між векторами. Вам не потрібно хвилюватися, що через це вектори однакового розміру.

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


3
Вам потрібно уважніше придивитися до другої частини питання.
Марк Викуп

@MarkRansom Так. Пропущено 2. Оновлено.
NathanOliver

13

Чи безпечно поміняти два різні вектори на C ++, використовуючи метод swap std :: vector ::?

Так. Обмін можна вважати безпечним. З іншого боку, безпека є суб'єктивною та відносною і може розглядатися з різних точок зору. Таким чином, неможливо дати задовільну відповідь, не доповнюючи питання контекстом і не вибираючи, яку безпеку розглядають.

Чи все-таки безпечно поміняти два вектори методом swap std :: vector ::: WidgetVector.swap (Widget2Vector); або це призведе до розвитку УБ?

УБ не буде. Так, це все ще безпечно в тому сенсі, що програма неправильно сформована.


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
Правильний результат, неправильні міркування.
NathanOliver

2
@AdrianMole Хоча існує спеціалізація, для std::swapякої не використовується ОП. Коли ви це робите First.swap(Second);, ви телефонуєте, std::vector::swapяка відрізняється від функціїstd::swap
NathanOliver

1
Вибачте, мій поганий ... Ви посилаєтеся прямо на документацію std :: своп-спеціалізації для векторів. Але це відрізняється від функції члена , яка існує і використовується в питаннях (лише).
Аконкагуа

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.


0

using std::swap; swap(a, b);і a.swap(b);мають точно таку саму семантику, де працює остання; принаймні для будь-якого розумного типу. У цьому плані всі типові типи здорові.

Якщо ви не використовуєте цікавий розподільник (маючи на увазі стаціонарний, не завжди рівний і не розповсюджується на свопі контейнерів, див. std::allocator_traits), Поміняти два std::vectors одним і тим же шаблоном-аргументами - це просто нудна заміна трьох значень (для ємності, розміру та даних, вказівник). І обмін базовими типами, відсутніми перегонами даних, безпечний і не може кинути.

Це навіть гарантується стандартом. Див std::vector::swap().

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.