减少临时对象到就地施工的分配

时间:2016-12-30 21:29:59

标签: c++ optimization assignment-operator placement-new

使用std::list支持移动语义作为示例。

std::list<std::string> X;
... //X is used in various ways
X=std::list<std::string>({"foo","bar","dead","beef"});

编译器从C ++ 11开始进行赋值的最简单方法是:

  1. destroy X
  2. 构建std::list
  3. std::list移至X
  4. 现在,编译器不允许执行以下操作:

    1. 摧毁X
    2. 就地{/ 1}}
    3. 因为虽然这明显节省了另一个std::list,但它消除了分配。使第二种行为成为可能和可用的便捷方法是什么?它是否计划在未来的C ++版本中使用?

      我的猜测是,除了写作之外,C ++仍然没有提供:

      memcpy

      我是对的吗?

4 个答案:

答案 0 :(得分:2)

您实际上可以通过定义operator =来获取初始化列表。 对于std :: list,只需调用

    X = {"foo","bar","dead","beef"}.

在您的情况下,实际发生的事情是:

  1. 构建临时
  2. 使用临时
  3. 在X上调用移动赋值运算符

    对于大多数对象,例如std :: list,与简单构造对象相比,这实际上并不昂贵。

    但是,它仍会为第二个std :: list的内部存储带来额外的分配,这可以避免:如果可能的话,我们可以重用已经为X分配的内部存储。发生的事情是:

    1. 构造:临时为元素分配一些空间
    2. 移动:指针移动到X;释放X之前使用的空间
    3. 某些对象重载赋值运算符以获取初始值设定项列表,std::vectorstd::list就是这种情况。这样的运营商可以使用已经在内部分配的存储,这是最有效的解决方案。

      //请在此处插入关于过早优化的常规说法

答案 1 :(得分:1)

  

是否计划在未来的C ++版本中使用?

没有。谢天谢地。

分配与destroy-then-create相同。您的任务示例中未销毁XX是一个活动对象; X内容可能会被销毁,但X本身永远不会被销毁。并且也不应该

如果你想要销毁X,那么你就可以使用explicit-destructor-and-placement-new。虽然由于const成员的可能性,如果您想要安全,您还需要清洗指向该对象的指针。但是,永远不会被视为等同于此。

如果您关心效率,那么使用assign成员函数要好得多。通过使用assignX有机会重用现有分配。而这几乎肯定会使它比你的&#34; destroy-plus-construct&#34;更快。版。将链表移动到另一个对象的成本是微不足道的;不得不销毁所有这些分配而仅重新分配它们的成本不是。

这对于std::list尤其重要,因为它有批次的分配。

最糟糕的情况是,assign的效率不会低于你在课堂外提出的任何其他方面。最好的情况是,它会好得多。

答案 2 :(得分:1)

当您有涉及移动分配的声明时:

x = std::move(y);

在移动之前,不会为x调用析构函数。但是,在移动之后,在某些时候将为y调用析构函数。移动赋值运算符背后的想法是它可以以一种简单的方式将y的内容移动到x(例如,将指针复制到y的存储器x)。它还必须确保其先前的内容被正确销毁(它可能选择与y交换它,因为您知道y可能不再使用,而y的析构函数将会被称为。

如果内联移动分配,编译器可能会推断出将存储从y移动到x所需的所有操作都等同于就地构造。

答案 3 :(得分:1)

重新提出你的最后一个问题

  

我是对的吗?

没有

您对什么是允许或不允许的想法是错误的。只要保留了可观察的效果,就允许编译器替换任何优化。这被称为&#34;好像&#34;规则。可能的优化包括删除所有代码,如果它不影响任何可观察的代码。特别是,您的&#34;不允许&#34;对于第二个例子是完全错误的,并且推理&#34;它消除了分配&#34;也适用于你的第一个例子,你得出相反的结论,即那里存在自相矛盾。