减少将临时对象分配给现场施工

Reducing assignment of temporary object to in-place construction

本文关键字:现场 施工 分配 临时对象      更新时间:2023-10-16

以支持移动语义的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. 销毁X
  2. 构造std::list
  3. std::list移动到X

现在,编译器不允许执行以下操作:

  1. 销毁X
  2. 施工std::list到位

因为虽然这显然节省了另一个memcpy,但它消除了分配。使第二行为成为可能和可用的方便方法是什么?它在C++的未来版本中有计划吗?

我的猜测是,C++仍然没有提供这一点,除非写:

X.~X();
new(&X) std::list<std::string>({"foo","bar","dead","beef"});

我说得对吗?

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

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

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

  1. 构建临时
  2. 用临时

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

然而,它仍然会对第二个std::list的内部存储进行额外的分配,这是可以避免的:如果可能的话,我们可以重用已经分配给X的内部存储。正在发生的是:

  1. 构造:临时为元素分配一些空间
  2. 移动:指针移动到X;X之前使用的空间被释放

一些对象重载赋值运算符以获取初始值设定项列表,std::vector和std::list就是这样。这样的操作员可以使用内部已经分配的存储,这是最有效的解决方案。

//请在这里插入关于过早优化的常见漫谈

它在C++的未来版本中有计划吗?

否。谢天谢地。

赋值是而不是与销毁然后创建相同。X在您的作业示例中未被销毁。CCD_ 9是一个活动对象;X内容可能被破坏,但X本身永远不会被破坏。

如果你想销毁X,那么你有这个能力,使用显式析构函数和placementnew。尽管由于const成员的可能性,如果您希望安全,您还需要清洗指向对象的指针。但是赋值不应该被认为是等价的。

如果您关心效率,那么最好使用assign成员函数。通过使用assignX有机会重用现有的分配。这几乎肯定会使它比你的"破坏加构造"版本更快。将一个链表移动到另一个对象中的成本是微不足道的;不得不销毁所有这些分配,只为再次分配而付出的代价并非如此。

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

在最坏的情况下,assign的效率不会低于你从课堂外想出的任何其他方法。最好的情况是,情况会好得多。

当您有一个涉及移动分配的语句时:

x = std::move(y);

在执行移动之前,不会为x调用析构函数。然而,在移动之后,在某个时刻,将为y调用析构函数。移动赋值运算符背后的思想是,它可能能够以一种简单的方式将y的内容移动到x(例如,将指向y的存储的指针复制到x)。它还必须确保其以前的内容被正确地销毁(它可以选择与y交换,因为你知道y可能不再使用,并且y的析构函数将被调用)。

如果移动赋值是内联的,编译器可能能够推断出将存储从y移动到x所需的所有操作都相当于就地构造。

关于最后一个问题

我说得对吗?

否。

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