使用 std::vector::swap 方法在 C++ 中交换两个不同的向量是不是安全?

Posted

技术标签:

【中文标题】使用 std::vector::swap 方法在 C++ 中交换两个不同的向量是不是安全?【英文标题】:Is it safe to swap two different vectors in C++, using the std::vector::swap method?使用 std::vector::swap 方法在 C++ 中交换两个不同的向量是否安全? 【发布时间】:2020-04-13 04:31:12 【问题描述】:

假设你有以下代码:

#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?

【问题讨论】:

【参考方案1】:

是的,交换相同类型的向量是完全安全的。

底层的向量只是指向向量使用的数据和序列“结束”的几个指针。当您调用 swap 时,您只需在向量之间交换这些指针。因此,您不必担心向量的大小相同。

不能使用swap 交换不同类型的向量。您需要实现自己的函数来进行转换和交换。

【讨论】:

您需要更仔细地查看问题的第二部分。 @MarkRansom 是的。错过了2。已更新。【参考方案2】:

这是安全的,因为在交换操作期间没有创建任何内容。只有 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 

如您所见,只有数据成员 ptrn 在成员函数 swap 中被交换。没有使用额外的资源。

std::vector 类中使用了类似的方法。

至于这个例子

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

那么就有不同类的对象。成员函数swap应用于相同类型的向量。

【讨论】:

但是 OP 的 actual 情况是什么,被交换的向量属于 不同的 类? @AdrianMole 如果为给定的向量类型定义了成员函数交换。它没有为不同类型的向量定义。它不是模板成员函数。 "swap 应用于相同类型的向量" 你应该在 "is" 和 "applied" 之间添加一个 "only"。 当然,有状态的分配器可能会改变一些事情。【参考方案3】:

在 C++ 中使用 std::vector::swap 方法交换两个不同的向量是否安全?

是的。交换通常被认为是安全的。另一方面,安全性是主观的和相对的,可以从不同的角度考虑。因此,如果不通过上下文扩充问题并选择正在考虑的安全类型,就不可能给出令人满意的答案。

用 std::vector::swap 方法交换两个向量是否仍然安全:WidgetVector.swap(Widget2Vector);还是会导致 UB?

不会有UB。是的,从程序格式错误的意义上说,它仍然是安全的。

【讨论】:

【参考方案4】:

swap 函数定义如下:void swap( T&amp; a, T&amp; b );。请注意,ab 是(并且必须是)同一类型。 (没有用这个签名定义这样的函数:void swap( T1&amp; a, T2&amp; b ),因为它没有任何意义!)

同样,std::vector类的swap()成员函数定义如下:

template<class T1> class vector // Note: simplified from the ACTUAL STL definition

//...
public:
    void swap( vector& other );
//...
;

现在,由于函数参数(格式为:template &lt;typename T2&gt; void swap(std::vector&lt;T2&gt;&amp; other))没有模板覆盖(参见Explicit specializations of function templates)的“等效”定义,因此该参数必须是 与“调用”类具有相同类型(模板)的向量(即,它也必须是vector&lt;T1&gt;)。

您的std::vector&lt;Widget&gt;std::vector&lt;Widget2&gt; 是两种不同的 类型,因此对swap 的调用不会编译,无论您是否尝试使用成员函数任何一个对象(就像您的代码一样),或者使用std::swap() 函数的特化,该函数将两个std:vector 对象作为参数。

【讨论】:

std::vector::swap 是成员函数,怎么可能是独立模板函数的特化??? @Aconcagua 虽然我不是 exact 术语方面的专家,但请参阅此处:cppreference,其中说明了以下内容:“将 std::swap 算法专门用于 std::vector " @AdrianMole 虽然确实存在std::swap 的特化,但这不是 OP 使用的。当您执行First.swap(Second); 时,您调用std::vector::swap,这是与std::swap 不同的功能 对不起,我的错...您的链接直接指向向量的 std::swap 专业化文档。但这与member function 不同,member function 也存在并且在问题中使用(仅)。 推理实际上是类似的:成员被定义为void swap(std::vector&amp; other)(即std::vector&lt;T&gt;),而不是template &lt;typename U&gt; void swap(std::vector&lt;U&gt;&amp; other)(假设T是向量本身的类型参数)。【参考方案5】:

您不能交换两种不同类型的向量,但这是编译错误而不是 UB。 vector::swap 只接受相同类型和分配器的向量。

不确定这是否可行,但如果您想要一个包含从 Widgets 转换而来的 Widget2s 的向量,您可以试试这个:

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

Widget2 必须可以从 Widget 移动构造。

【讨论】:

【参考方案6】:

using std::swap; swap(a, b);a.swap(b); 在后者工作的地方具有完全相同的语义;至少对于任何理智的类型。在这方面,所有标准类型都是合理的。

除非您使用有趣的分配器(意思是有状态的,并不总是相等的,并且不会在容器交换时传播,请参阅std::allocator_traits),否则使用相同的模板参数交换两个 std::vectors 只是三个值的无聊交换(用于容量、大小和数据指针)。并且交换基本类型,没有数据竞争,是安全的,不会抛出。

标准甚至可以保证这一点。见std::vector::swap()

【讨论】:

以上是关于使用 std::vector::swap 方法在 C++ 中交换两个不同的向量是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章

Objective C:何时在 App Delegate 中使用方法以及何时在 View Controller 中使用方法

在 Dao 类中使用静态方法还是非静态方法?

如何在 kotlin 中使用非静态方法?

在C#中啥是匿名方法?使用它都有哪些好处?

模型类(在 MVC 中)应该使用静态方法还是实例方法?

使用 Mockery 模拟在另一个静态方法中调用的静态方法