简体   繁体   English

有效使用 std::move 将所有 object 个实例聚合到一个 std 容器中

[英]efficient use of std::move to aggregate all object instances in a std container

I need to centrally accumulate certain entity creations in my program into a container and I want to use the efficiency of std::move with a move constructor to aggregate all entities created anwhere in the program into one container witout extra copying or instance allocations.我需要将我程序中的某些实体创建集中地累积到一个容器中,我想使用 std::move 的效率和一个移动构造函数来将程序中任意位置创建的所有实体聚合到一个容器中,而无需额外的复制或实例分配。 Unfortunately using the most popular std::vector container brings with it vector internal management overhead ( or is that compiler implementation dependent??)不幸的是,使用最流行的 std::vector 容器会带来向量内部管理开销(或者依赖于编译器实现??)

For example例如

class Item {
public :
  static int Count;
  int ID;

  Item() : ID(Count++)
  { cout<<"   Item CREATED - ID:"<<ID<<endl; }

  Item(const Item &itm) : ID(Count++)
  { cout<<"   Item COPY CREATED - (ID:"<<ID<<") <= (ID:"<<itm.ID<<")\n"; }

  Item(const Item &&itm) : ID(Count++)
  { cout<<"   Item MOVE CREATED - (ID:"<<ID<<") <= (ID:"<<itm.ID<<")\n"; }

  ~Item() { cout<<" Item DELETED - (ID:"<<ID<<") \n"; }
};

int Item::Count = 0;

void VectorOfItemTest() {
  std::vector<Item> ItemVec;

  for(int idx=0; idx<3; idx++) {
    std::cout<<" { loop "<<idx<<std::endl;
    Item itemInst;
    ItemVec.push_back(std::move(itemInst));
    std::cout<<" } "<<idx<<std::endl<<std::endl;
  }
}

produces output:产生 output:

-----------------------------
 { loop 0
   Item CREATED - ID:0
   Item MOVE CREATED - (ID:1) <= (ID:0)
 } 0

   Item DELETED - (ID:0)
 { loop 1
   Item CREATED - ID:2
   Item MOVE CREATED - (ID:3) <= (ID:2)
   Item COPY CREATED - (ID:4) <= (ID:1)
   Item DELETED - (ID:1)
 } 1

   Item DELETED - (ID:2)
 { loop 2
   Item CREATED - ID:5
   Item MOVE CREATED - (ID:6) <= (ID:5)
   Item COPY CREATED - (ID:7) <= (ID:4)
   Item COPY CREATED - (ID:8) <= (ID:3)
   Item DELETED - (ID:4)
   Item DELETED - (ID:3)
 } 2

   Item DELETED - (ID:5)
   Item DELETED - (ID:7)
   Item DELETED - (ID:8)
   Item DELETED - (ID:6)

Is it possible to avoid the extra copy-s that causes matching delete-s inside the for loop?是否有可能避免导致 for 循环内匹配删除的额外复制?

Is there a container (or can we use std::vector in any way) where we get all loop outputs looking as follows?是否有一个容器(或者我们可以以任何方式使用 std::vector )让所有循环输出如下所示?

 { loop X
   Item CREATED - ID:X
   Item MOVE CREATED - (ID:X+1) <= (ID:X)
 } X
 Item DELETED - (ID:X)

I have looked at Why std::move is required to invoke move assign operator of std::vector Why does std::move copy contents for a rvalue or const lvalue function argument?我看过为什么需要 std::move 来调用 std::vector 的移动赋值运算符为什么 std::move 复制右值或 const 左值 function 参数的内容? and a few others here but its still not clear how I can use std::vector (or other containers) efficiently with using std::move.和这里的其他一些,但仍然不清楚我如何通过使用 std::move 有效地使用 std::vector(或其他容器)。

I found a rejected question Hard time understanding object lifetime, copy, move constructor asking close to what I am referring to here I guess.我发现一个被拒绝的问题Hard time understanding object lifetime, copy, move constructor asking close to what I am referenced here I guess.

There are two things required to get your stated ideal:要达到你的理想状态需要两件事:

First, you'll have to mark your move constructor as noexcept .首先,您必须将移动构造函数标记为noexcept And if it then throws an exception, std::terminate is called, so it really must be designed so that it never throws.如果它随后抛出异常,则会调用std::terminate ,因此必须将其设计为永远不会抛出异常。

Item(const Item &&itm) noexcept : ID(Count++)
{ cout<<"   Item MOVE CREATED - (ID:"<<ID<<") <= (ID:"<<itm.ID<<")\n"; }

This gets you down to:这使您可以:

{ loop 0
   Item CREATED - ID:0
   Item MOVE CREATED - (ID:1) <= (ID:0)
 } 0

 Item DELETED - (ID:0) 
 { loop 1
   Item CREATED - ID:2
   Item MOVE CREATED - (ID:3) <= (ID:2)
   Item MOVE CREATED - (ID:4) <= (ID:1)
 Item DELETED - (ID:1) 
 } 1

 Item DELETED - (ID:2) 
 { loop 2
   Item CREATED - ID:5
   Item MOVE CREATED - (ID:6) <= (ID:5)
   Item MOVE CREATED - (ID:7) <= (ID:3)
   Item MOVE CREATED - (ID:8) <= (ID:4)
 Item DELETED - (ID:3) 
 Item DELETED - (ID:4) 
 } 2

 Item DELETED - (ID:5) 
 Item DELETED - (ID:6) 
 Item DELETED - (ID:7) 
 Item DELETED - (ID:8) 

The only thing that has changed above is that your copies from before are turned into moves.上面唯一改变的是你之前的副本变成了移动。

The reason this is needed is so that vector::push_back can maintain the strong exception guarantee from C++98/03.需要这样做的原因是vector::push_back可以维护来自 C++98/03 的强异常保证。 This means that if any exception is thrown during the push_back , there are no changes to the value of the vector .这意味着如果在push_back期间抛出任何异常,则vector的值不会发生变化。

Second, you need to reserve sufficient space in the vector so that push_back never needs to allocate a bigger buffer:其次,您需要在向量中reserve足够的空间,以便push_back永远不需要分配更大的缓冲区:

std::vector<Item> ItemVec;
ItemVec.reserve(3);

This gets you down to the ideal:这让你达到理想状态:

 { loop 0
   Item CREATED - ID:0
   Item MOVE CREATED - (ID:1) <= (ID:0)
 } 0

 Item DELETED - (ID:0) 
 { loop 1
   Item CREATED - ID:2
   Item MOVE CREATED - (ID:3) <= (ID:2)
 } 1

 Item DELETED - (ID:2) 
 { loop 2
   Item CREATED - ID:4
   Item MOVE CREATED - (ID:5) <= (ID:4)
 } 2

 Item DELETED - (ID:4) 
 Item DELETED - (ID:5) 
 Item DELETED - (ID:3) 
 Item DELETED - (ID:1) 

When push_back is called with ItemVec.size() == ItemVec.capacity() , a new buffer is allocated, and all of the existing elements are moved to the new buffer... unless your move constructor is not noexcept , in which case all of the existing elements are copied to the new buffer.当使用ItemVec.size() == ItemVec.capacity()调用push_back时,将分配一个新缓冲区,并将所有现有元素移动到新缓冲区...除非您的移动构造函数不是noexcept ,在这种情况下所有现有元素都被复制到新缓冲区。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM