std :: vector在增长时会调用swap函数吗?总是或仅适用于某些类型?

时间:2010-03-04 09:46:21

标签: c++ vector swap

据我所知,我可以使用向量向量(std::vector< std::vector<int> >),这将非常有效,因为内部元素不会被复制,而是交换,这要快得多,因为不包括复制内存缓冲区。我是对的吗?

std::vector何时恰好使用交换功能?我在C++ standard找不到任何相关信息。是否在缓冲区重新分配期间发生?

我做了一些测试才发现它,但我失败了。根本不调用我的自定义数据类型的交换函数。

编辑:这是我的test program

7 个答案:

答案 0 :(得分:8)

我没有链接来支持这些声明,但据我所知,与Microsoft C ++一起分发的STL实现使用一些内部非标准魔术注释来将vector(和其他STL集合)标记为具有-performant-swap所以vector<vector<>>不会复制内部向量但交换它们。直到VC9,在VC10中,它们将切换到rvalue-references。我认为你不应该像没有交叉编译器的方式那样标记你自己的类,你的代码只能在特定的编译器版本上工作。

编辑:我快速浏览了VC9中的<vector>标题,发现了这个:

    // vector implements a performant swap
template <class _Ty, class _Ax>
    class _Move_operation_category<vector<_Ty, _Ax> >
    {
    public:
        typedef _Swap_move_tag _Move_cat;
    };

只是为了实验,你可以尝试为你自己的类型专门化这个类,但正如我所说,这是特定于STL版本的,它在VC10中消失

答案 1 :(得分:3)

std :: vector传统上在生长时将元素复制到新内存中,然后销毁旧值。但是,在即将推出的带有rvalue引用和移动语义的C ++ 0x中,std :: vector可以将构造元素移动到新内存中。这样效率更高。如果你有一个字符串向量或一些其他昂贵的复制数据,那么移动构造它们本质上只是将指针复制到保持的数据并将源对象标记为空。与复制和销毁相比,这非常便宜,并且有效地解决了移动可构造类型的昂贵的向量重新分配问题。这几乎是你所描述的交换优化,内置于语言中。

答案 2 :(得分:3)

我认为不允许使用swap(由ADL找到)。我没有明确禁止找到,但对矢量值类型的要求是CopyConstructible和Assignable。这些都没有swap作为有效操作(甚至是可选操作),或者任何标准方法来确定交换是否过载。可能它可以在适当的地方使用std::swap(或特殊化,如果存在),因为它对其参数有相同的要求:CopyConstructible和Assignable(以及命名空间std中函数的UDT的特化必须实现定义的行为通用模板)。但是,这对重新分配没有帮助,因为你必须构造一些“空”对象来交换,而且当标准没有时,向量不能只决定它自己的权限,它的值类型应该是默认可构造的。不需要。

我认为假设如果类型T不需要操作,那么即使编译器以某种方式在心理上确定它存在,也不会执行它是合理的。在C ++中,仅仅因为某些东西具有为实现接口而定义的正确函数并不意味着它声称实现了该接口的语义。直到你在需要语义的上下文中传递它,你声称函数的行为与接口的预期一致。该标准不要求向量的值类型表现良好swap,因此向量的实现不能假设仅因为swap被定义,它优于operator=

这只是我对规范意图的解释,但我无论如何都找不到任何确定的东西。

23.2.4.3/4给出了一个线索。在谈到erase时,它说:“T的赋值运算符被称为等于删除元素后向量中元素数量的次数”。因此,明确禁止向量使用swap在擦除后向前移动向量的末尾:必须使用operator=。我认为这是一个强烈的暗示,即作者的期望是使用operator=作为一切,否则他们就不会如此粗心,以至于在实际可以使用的情况下禁止swap无需任何默认构造函数。

我也看到微软的观点,由你和jdv描述,对于容器容器来说,交换有很大的收获。只要“模板魔术”不会干扰格式良好的C ++程序,实现就可以提供类型的方法来告诉 vector交换。

例如,他们可能有一个类型特征模板,名称中带有双下划线。使用该名称的效果是实现定义的,因此所有的赌注都是关闭的。 C ++标准没有说明std :: vector在专门用于该模板的程序中的行为。在程序中使用保留名称后,实现可以定义向量,以便对所有标准关注使用operator=swapaubergine

答案 3 :(得分:1)

标准说的是什么,我不确切知道。 stl中的默认值是复制,如果你经常编辑向量的向量,那就没有用了。

但是,您想要的行为是在Visual C ++的TR1实现中实现的,可以作为VS2008的更新(TR1是C ++ 0x标准的前奏)。他们像许多其他编译器供应商一样从Dinkumware购买他们的stl实现,所以你可以期望看到它出现在其他编译器上。见http://msdn.microsoft.com/en-us/library/bb982198.aspx

如果您使用GCC,这对您没用,但这里可能有其他人可以告诉您。

[编辑] 在编辑后阅读,我发现microsoft声称swap()优化是他们的功能,而不是dinkimware。至少我在阅读这篇博文时是这样的:http://blogs.msdn.com/vcblog/archive/2008/01/08/q-a-on-our-tr1-implementation.aspx

答案 4 :(得分:1)

基本上,您在询问执行以下操作时会发生什么:

vector<int> v;
v.reserve(100);

我们可以看看libstdc ++在这种情况下的作用是example

template<typename _Tp, typename _Alloc> void vector<_Tp, _Alloc>::reserve(size_type __n) {
    if (__n > this->max_size())
        __throw_length_error(__N("vector::reserve"));
    if (this->capacity() >= __n)
        return;

    const size_type __old_size = size();
    pointer __tmp = _M_allocate_and_copy(__n,
        _GLIBCXX_MAKE_MOVE_ITERATOR(this->_M_impl._M_start),
        _GLIBCXX_MAKE_MOVE_ITERATOR(this->_M_impl._M_finish));
    std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish, _M_get_Tp_allocator());
    _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage - this->_M_impl._M_start);
    this->_M_impl._M_start = __tmp;
    this->_M_impl._M_finish = __tmp + __old_size;
    this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n;
}

这里的重要电话是_M_allocate_and_copy

template<typename _ForwardIterator> pointer _M_allocate_and_copy(size_type __n, _ForwardIterator __first, _ForwardIterator __last) {
    pointer __result = this->_M_allocate(__n);
    std::__uninitialized_copy_a(__first, __last, __result, _M_get_Tp_allocator());
    return __result;
}

这里的重要电话是std::__uninitialized_copy_a

template<typename _InputIterator, typename _ForwardIterator, typename _Allocator> _ForwardIterator __uninitialized_copy_a(_InputIterator __first, _InputIterator __last, _ForwardIterator __result, _Allocator& __alloc) {
    _ForwardIterator __cur = __result;
    for (; __first != __last; ++__first, ++__cur)
        __alloc.construct(&*__cur, *__first);
    return __cur;
}

这是致电construct。如您所见,它正在使用复制构造函数。

void construct ( pointer p, const_reference val ) {
    new ((void*)p) T (val);
}

因此,当重新分配发生时,向量中的每个元素都会在其上调用复制构造函数。

答案 5 :(得分:0)

不幸的是,在讨论C ++ 0x标准时忽略了使用交换功能。

在我看来,交换应该是语言级别已知的基本功能。它解决了为语言添加右值引用的许多合理性。

从函数返回std :: vector或从临时分配可以使用swap而不是copy。容器可以使用它来优化重新分配。

唉。 :(

答案 6 :(得分:-2)

如果你有一个非常大的向量并且想要释放它,你可以使用交换函数将它与空向量交换。这是使用STL容器时释放内存空间的一种非常有效的方法。