在Herb Sutter的CppCon 2014演讲“基础知识:现代C++风格”中,他在幻灯片28(a web copy of the slides are here)中引用了此模式:

class employee {
  std::string name_;
public:
  void set_name(std::string name) noexcept { name_ = std::move(name); }
};

他说,这是有问题的,因为在使用临时调用set_name()时,noexcept-ness不强(他使用短语“noexcept-ish”)。

现在,我在自己最近的C++代码中已经大量使用了上述模式,主要是因为它使我每次都不必键入两个set_name()副本-是的,我知道每次强制执行一个副本构造可能会有点效率低下,但嘿,我是一个懒惰的打字员。但是,Herb的短语“This noexcept是有问题的”令我担心,因为我在这里没有遇到问题:std::string的移动赋值运算符是noexcept,它的析构函数也是如此,因此对我来说,上面的set_name()似乎可以保证noexcept。我确实看到编译器在准备参数时在set_name()之前抛出了潜在的异常,但是我正努力将其视为有问题的。

后来在幻灯片32上,赫伯清楚地指出以上内容是反模式。有人可以向我解释为什么我懒得写不好的代码吗?

最佳答案

其他人已经介绍了上面的noexcept推理。

Herb在效率方面的讨论花费了更多时间。问题不在于分配,而在于不必要的释放。当您将一个std::string复制到另一个ojit_code时,如果有足够的空间容纳要复制的数据,则复制例程将重用已分配的目标字符串存储。在进行移动分配时,目标字符串的现有存储必须被重新分配,因为它从源字符串继承了存储。 “复制并移动”惯用语迫使总是重新分配,即使您没有通过临时分配也是如此。这是在稍后的演讲中演示的可怕表现的根源。他的建议是改为使用const ref,如果您确定需要使用const ref,则对r值引用有重载。这将为您提供两全其美的优势:将非临时人员复制到现有存储中,避免了重新分配,而为临时人员移动,而您将以一种方式或另一种方式为重新分配支付费用(目的地在移动之前取消分配,或者复制后,源将取消分配)。

上面的内容不适用于构造函数,因为成员变量中没有要分配的存储空间。这很好,因为构造函数通常会使用多个参数,并且如果您需要为每个参数执行const ref/r-value ref重载,则最终会产生构造函数重载的组合爆炸。

现在的问题是:复制时有多少类可以重用std::string之类的存储?我猜std::vector确实可以,但是我不确定。我确实知道我从未写过这样的可重用存储的类,但是我写了很多包含字符串和 vector 的类。遵循Herb的建议,对于不会重复使用存储的类不会造成伤害,首先将使用接收器功能的复制版本进行复制,如果您确定复制对性能造成的影响太大,则您会进行r值引用重载以避免复制(就像对std::string那样)。另一方面,对于“std::string”和其他重用存储的类型,使用“复制并移动”确实会降低性能,并且这些类型在大多数人的代码中可能会大量使用。我现在正在遵循Herb的建议,但是在我认为问题完全解决之前(我可能没有足够的时间来潜伏这一切的博客文章),需要多考虑一些问题。

关于c++ - 为什么在Herb Sutter的CppCon 2014演讲中不建议采用赋值 setter 成员函数(返回基础知识: Modern C++ Style)?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26261007/

10-09 06:38