这是在C ++ 11中将一个std :: vector的内容移动到另一个的结尾的最有效方法吗?

时间:2013-05-16 23:34:36

标签: c++ algorithm c++11

我原以为vector::insert()std::copy()命令需要额外分配。但是,如果我push_back()新创建的元素然后swap(),我认为只要包含的类型没有使用默认构造函数分配,这将减少任何分配。

我的问题实际上是针对std::vector类型的std::string,但应该适用于此处所述的其他类型:

template <typename T>
void appendMove(std::vector<T>& dst, std::vector<T>& src)
{
    dst.reserve(dst.size() + src.size())
    for(std::vector<T>::iterator it = src.begin(); it != src.end(); ++it)
    {
        dst.push_back(std::vector<T>());
        std::swap(dst.end()[-1], *it);
    }
}

我说错了吗?我错过了什么吗?也许还有更好的方法可以做到这一点?

1 个答案:

答案 0 :(得分:12)

效果免责声明:使用分析。

性能考虑因素:

    如果向量的容量足以插入元素,则
  • push_back必须检查每个调用。编译器不太可能足够智能以避免在循环内进行检查,也就是说,对于它必须检查的每个循环迭代,这也可能禁止进一步优化。
  • 如果之前没有调用reservepush_back必须在旅途中调整向量的容量,可能会在循环内多次调整,这会导致已经移动存储的元素。
  • swapmove略有不同:move对移动的对象的保证不太严格,这可能允许优化
  • 正如GManNickG在评论中指出的那样,vector::insert可以在插入之前保留必要的内存,因为它可以插入整个范围。这可能需要对随机访问迭代器进行专门化,因为它们的std::difference在O(1)中(它可以应用于所有双向迭代器,但这可能比两个循环迭代更慢 - 而不是不是< / em>预订)。

我能想到的最有效的方法是保留必要的容量,然后在没有容量检查的情况下插入元素(通过push_back或通过insert)。

智能标准库实施可以调用reserve内的insert,而不会在插入期间检查容量。尽管如此,我还不完全确定这会comply to the Standard

如果你的图书馆足够聪明,Andy Prowl的版本(见评论)就足够了:

dst.insert( dst.end(),
            std::make_move_iterator(src.begin()),
            std::make_move_iterator(src.end())    );

否则,您可以在调用reserve之前手动编写对insert的调用,但是您不能(AFAIK)插入/附加没有内部容量检查的元素:

template < typename T, typename FwdIt >
void append(FwdIt src_begin, FwdIt src_end, std::vector<T>& dst)
{
    dst.reserve( dst.size() + std::distance(src_begin, src_end) );
    // capacity checks might slow the loop inside `insert` down
    dst.insert(dst.end(), src_begin, src_end);
}

示例:

int main()
{
    std::vector<int> dst = { 0, 1, 2 };
    std::vector<int> src = { 3, 42 };

    append( std::make_move_iterator(src.begin()),
            std::make_move_iterator(src.end()),
            dst );
}

对不同的迭代器类型实现append可能更好:

template < typename T, typename FwdIt >
void append(FwdIt src_begin, FwdIt src_end, std::vector<T>& dst,
            std::forward_iterator_tag)
{
    // let the vector handle resizing
    dst.insert(dst.end(), src_begin, src_end);
}

template < typename T, typename RAIt >
void append(RAIt src_begin, RAIt src_end, std::vector<T>& dst,
            std::random_access_iterator_tag)
{
    dst.reserve( dst.size() + (src_end - src_begin) );
    dst.insert(dst.end(), src_begin, src_end);
}

template < typename T, typename FwdIt >
void append(FwdIt src_begin, FwdIt src_end, std::vector<T>& dst)
{
    append( src_begin, src_end, dst,
            typename std::iterator_traits<FwdIt>::iterator_category() );
}

如果由于循环内部的容量检查而出现性能问题,您可以尝试首先默认构造所需的其他元素。当它们存在时(即已构造),您可以使用未经检查的operator[]或简单迭代器将src对象移动到其目标:

template < typename T, typename RAIt >
void append(RAIt src_begin, RAIt src_end, std::vector<T>& dst,
            std::random_access_iterator_tag)
{
    auto src_size = src_end - src_begin;

    dst.resize( dst.size() + src_size );

    // copy is not required to invoke capacity checks
    std::copy( src_begin, src_end, dst.end() - src_size );
    // ^this^ should move with the example provided above
}

便利包装:

template < typename T, typename FwdIt >
void append_move(FwdIt src_begin, FwdIt src_end, std::vector<T>& dst)
{
    append( std::make_move_iterator(src_begin),
            std::make_move_iterator(src_end),
            dst );
}