当前位置: 首页 > 面试题库 >

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

柴嘉年
2023-03-14
问题内容

我当时以为vector::insert()and
std::copy()命令需要额外的分配。但是,如果我push_back()是一个新创建的元素,那么swap()我认为这将减少任何分配,只要所包含的类型未使用默认构造函数分配即可。

我的问题实际上是专门针对std::vectorstd::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);
    }
}

我对么?我还有其他东西吗?也许有更好的方法可以做到这一点?


问题答案:

性能免责声明:使用性能分析。

性能注意事项:

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

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

一个智能的标准库实现可以对reserve内部进行调用,insert而无需在插入过程中检查容量。不过,我不太确定这是否符合标准。

如果您的图书馆足够聪明,那么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 );
}


 类似资料:
  • 本文向大家介绍linux把一个文件的内容复制到另一个文件的末尾,包括了linux把一个文件的内容复制到另一个文件的末尾的使用技巧和注意事项,需要的朋友参考一下 问题描述: 比如11的文件内容是: hello 22的文件内容是: world 将22的文件内容复制到11文件的末尾,11文件的效果就是: hello world 解决办法: cat 22 >> 11 >>的意思是追加的意思 > 的意思是重

  • 问题内容: 我有两个长度未知的数组,我只想将一个附加到另一个的末尾,即: 我曾尝试使用,但似乎无法使其正常工作。 问题答案: 使用,应类似于以下内容:

  • 我在编写一个将方法参数列表中的所有元素追加到另一个列表末尾的方法时遇到了麻烦。如果列表被更改,该方法应该返回true,否则返回false。 例如,如果原始列表是1->6->5,而另一个列表是3->8->2。呼叫结束后,列表现在是1->6->5->3->8->2。 Boolean return语句给我带来了麻烦,因为我不清楚它们是如何链接到列表的逻辑中的。我也不知道指针需要移动多远才能追加列表。整件

  • 问题内容: 我需要删除单词的第一个字母并将其移到末尾,例如: 到目前为止,我已经尝试过了: 但是,我应该如何将第一个字母移到末尾? 问题答案: 您可以使用:

  • 问题内容: 我有一些数据结构,我想将其中一个用作临时结构,将另一个用作非临时结构。 现在的问题当然是实际上只是指向,因此一旦清除,也是如此。 如何在使用Java时保留值? 问题答案: 您可以使用以下技巧: 或使用 您可以在此处获取有关clone()方法的一些信息 但是您应该记住,所有这些方式都会给您 List 的副本,而不是其所有元素。因此,如果您更改复制的列表中的元素之一,则它也将在原始列表中进

  • 我一直在设计一个基于Swing的桌面RPG程序,以促进带有GUI控制元素的基于文本的角色扮演。 为了促进这一点,每个正在运行的客户端都会获得一个带有所有重要JFrames的主桌面(托管客户端上的“GM Desktop”和远程客户端上的“Player Desktop”)。此外,GM和Players都可以为角色打开“透视桌面”,为他们提供一个单独的JDesktopPane,其中包含提供该角色视角的“角

  • 问题内容: 假设我有数组和围棋。什么是追加的所有值最快的方式来? 问题答案: Go中的数组是次要的,而 切片 则是方法。Go提供了一个内置功能来附加切片: 输出: 在Go Playground上尝试一下。 笔记: Go中的数组是固定大小的:创建数组后,就无法增加其大小,因此无法向其添加元素。如果需要,您将需要分配一个更大的新数组。大到足以容纳2个数组中的所有元素。切片更加灵活。 Go中的数组是如此