繁体   English   中英

减少临时对象到现场施工的分配

[英]Reducing assignment of temporary object to in-place construction

以支持移动语义的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. 用临时调用X上的移动分配运算符

在大多数对象上,例如std :: list,与仅构造一个对象相比,这实际上并不昂贵。

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

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

一些对象使赋值运算符超载以获取初始化器列表, std :: vectorstd :: list就是这种情况。 这样的操作员可以使用已经在内部分配的存储,这是最有效的解决方案。

//请在此处插入有关过早优化的常见问题

在将来的C ++版本中计划了吗?

不用了,谢谢你。

分配是一样的破坏,然后创建。 X在您的分配示例中未销毁。 X是有生命的物体; X内容可能会被破坏,但X本身永远不会被破坏。 而且也不应该

如果您想销毁X ,那么您就可以使用显式析构和放置新函数来具有这种能力。 尽管感谢const成员的可能,但是如果您想确保安全,还需要清洗指向该对象的指针。 但是,分配永远不应该被认为是等效的。

如果您关心效率,那么最好使用assign成员函数。 通过使用assignX可以重用现有分配。 而且这几乎肯定会使它比您的“ destroy-plus-construct”版本更快。 将链接列表移动到另一个对象中的开销很小。 不必为了重新分配而销毁所有这些分配的成本。

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

在最坏的情况下, assign效率不会比您从课堂之外提出的其他效率低。 最好的情况是,情况会好得多。

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

x = std::move(y);

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

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

再问你最后一个问题

”“对吗?

没有。

您关于允许或不允许的想法是错误的。 只要编译器保留可观察的效果,就可以替换任何优化。 这称为“好像”规则。 如果不影响任何可见的代码,则可能的优化包括删除所有该代码。 特别是,您对第二个示例的“不允许”完全是错误的,而“它消除分配”的理由也适用于您的第一个示例,在该示例中您得出相反的结论,即那里存在自相矛盾。

暂无
暂无

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

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