代码之家  ›  专栏  ›  技术社区  ›  Baraa

当将一个向量附加到另一个向量时,为什么移动元素比复制元素更便宜?

  •  2
  • Baraa  · 技术社区  · 8 年前

    假设我有两个向量, src dst 我想附加 src 结束 夏令时 .

    我注意到,关于这项任务的大多数答案都建议:

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

    除此之外:

        dst.insert(dst.end(), src.begin(), src.end());
    

    据我所知,在这两种情况下,将元素推(插入)到向量需要在向量末尾为插入的元素分配空间,以确保内存连续性,我假设 copy move

    移动物体会使它们立即可以摧毁,这是这样做的唯一好处,还是我还缺少了其他东西?

    你能解释一下这两种情况吗

    1. 向量包含纯数据,例如:int。
    2. 向量包含类对象。
    3 回复  |  直到 8 年前
        1
  •  4
  •   Walter    8 年前

    事实上,如果复制和移动元素的成本相同(在您的示例中,是类型为 int ),则没有区别。

    std::string std::vector<something> ). 在这种情况下,移动或复制元素会产生(潜在的巨大)差异(前提是移动构造函数和 operator=(value_type&&) 正确实现/启用),因为移动仅将指针复制到分配的内存,而复制是深度的:它分配新内存并复制所有数据,包括递归深度复制(如果适用)。

    与存储在中的数据相关的成本 std::vector ,如果附加元素超过容量,则会有一些成本。在这种情况下,将调整整个矢量的大小,包括移动 全部的 std::vector 根据规范,将其所有元素存储在单个数组中。如果附加容器是代码中的常见操作,您可能需要考虑其他容器,例如 std::list std::deque .

        2
  •  3
  •   UmNyobe    8 年前

    我假设复制和移动成本相同。

    你认为这是错误的。

    ClassT::ClassT(const ClassT& orig);
    

    ClassT::ClassT(ClassT&& orig);
    

    You can always find a move constructor which is cheaper or equal to the copy constructor .

    当构造函数采用右值时,它被称为“移动构造函数” 引用作为参数。它没有义务移动任何东西 类不需要具有要移动的资源和“移动” (但可能不合理)参数为常数值的情况

    ClassT 例如,可以执行与复制构造函数完全相同的操作。或 阶级 应该 使用移动操作。

        3
  •  1
  •   StoryTeller - Unslander Monica    8 年前

    你已经有了答案。但我认为一个简短的程序可以很好地说明这一点:

    #include <iostream>
    #include <utility>
    #include <vector>
    #include <iterator>
    
    struct expansive {
        static unsigned instances;
        static unsigned copies;
        static unsigned assignments;
        expansive() { ++instances; }
        expansive(expansive const&) { ++copies; ++instances; }
        expansive& operator=(expansive const&) { ++assignments; return *this; }
    };
    
    unsigned expansive::instances = 0;
    unsigned expansive::copies = 0;
    unsigned expansive::assignments = 0;
    
    struct handle {
        expansive *h;
    
        handle() : h(new expansive) { }
        ~handle() { delete h; }
    
        handle(handle const& other) : h(new expansive(*other.h)) { }
        handle(handle&& other) : h(other.h) { other.h = nullptr; }
        handle& operator=(handle const& other) { *h = *other.h; return *this; }
        handle& operator=(handle&& other)  { std::swap(h, other.h); return *this; }
    };
    
    int main() {
    
      {
          std::vector<handle> v1(10), v2(10);
    
          v1.insert(end(v1), begin(v2), end(v2));
    
          std::cout << "When copying there were "
                    << expansive::instances   << " instances of the object with " 
                    << expansive::copies      << " copies and "
                    << expansive::assignments << " assignments made." << std::endl;
    
      }
    
        expansive::instances = expansive::copies = expansive::assignments = 0;
    
      {
        std::vector<handle> v1(10), v2(10);
    
          v1.insert(end(v1), std::make_move_iterator(begin(v2)),
                           std::make_move_iterator(end(v2)));
    
    
          std::cout << "When moving there were "
                    << expansive::instances   << " instances of the object with " 
                    << expansive::copies      << " copies and "
                    << expansive::assignments << " assignments made.\n";
      }
    
        return 0;
    }
    

    expansive 对复制成本非常高的资源建模(想象打开文件句柄、网络连接等)。这是由管理的资源 handle 为了保持程序的正确性, 手柄 当它本身被复制时,仍然必须执行昂贵的复制。

    现在,当程序运行时,它会产生以下输出:

    复制时,对象有30个实例,有10个副本,并且 进行了0个分配。移动时,有20个 具有0个副本和0个分配的对象。

    这是什么意思?这意味着,如果我们只想将句柄转移到另一个容器中,我们必须在整个过程中做一些非常广泛的工作(与我们试图转移的资源数量成线性关系)。移动语义在这里拯救了我们。
    通过使用 move_iterator ,实际昂贵的资源将被直接转移,而不是被多余地复制。这可以转化为性能的巨大提升。