为什么在Herb Sutter的CppCon 2014会话中不推荐使用值设置成员函数(返回基础:现代C ++风格)? [英] Why is value taking setter member functions not recommended in Herb Sutter's CppCon 2014 talk (Back to Basics: Modern C++ Style)?

查看:122
本文介绍了为什么在Herb Sutter的CppCon 2014会话中不推荐使用值设置成员函数(返回基础:现代C ++风格)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Herb Sutter的CppCon 2014谈话回到基础:现代C ++风格他在幻灯片28(幻灯片的网络副本在这里):

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

他说这是有问题的,因为当调用set_name()和临时的noexcept-



现在,我一直在我最近的C ++代码中使用上述模式,主要是因为它节省了我每次打字两份set_name() - 是的,我知道,通过强制一个副本构造每次都可能是有点低效,但是我是一个懒惰的typer。但是Herb的短语This noexcept is problematic 担心我,因为我没有得到这里的问题:std :: string的移动赋值运算符是noexcept,因为它的析构函数,所以set_name()我保证noexcept。我确实看到编译器 set_name()之前抛出一个潜在的异常,因为它准备的参数,但我很难看到这是有问题的。



稍后在幻灯片32 Herb清楚地说明上述是一种反模式。

解决方案

其他人已经涵盖了 noexcept 推理。



Herb在效率方面花了更多的时间谈论。问题不是与分配,它与不必要的释放。当您将一个 std :: string 复制到另一个时,如果有足够的空间来保存正在复制的数据,复制例程将重新使用目标字符串的分配存储。当执行移动分配时,目标字符串的现有存储必须被释放,因为它从源字符串接管存储。 复制和移动习语强制解除分配始终发生,即使您没有通过临时。这是可怕的性能的来源,在演讲后面演示。他的建议是改为使用一个const引用,如果你确定你需要它有一个重载的r值引用。这将给你最好的两个世界:复制到现有存储非临时性,避免解除分配和临时移动,你要支付一个或多个解除分配(移动之前目标释放或其他)该源代码在复制后释放)。



上述不适用于构造函数,因为在成员变量中没有存储要释放。这是很好的,因为构造函数通常需要多个参数,如果你需要对每个参数做const ref / r-ref ref重载,最后是构造函数重载的组合爆炸。



问题现在变成:有多少类有复用存储像std :: string复制时?我猜std :: vector是,但在外面,我不知道。我知道,我从来没有写过一个类,像这样重用存储,但我写了很多类,包含字符串和向量。以下Herb的建议不会伤害你的类不重用存储,你会复制的复制版本的汇功能,如果你确定复制是太多的性能打击,你会使r值引用重载以避免副本(就像你对std :: string一样)。另一方面,使用复制和移动确实对std :: string和其他类型的重用存储的性能有影响,这些类型可能在大多数人的代码中看到很多使用。我现在正在跟随Herb的建议,但是在我考虑这个问题完全解决之前,需要考虑一些这样的问题(可能有一篇博文,我没有时间写所有这一切潜伏) p>

In Herb Sutter's CppCon 2014 talk Back to Basics: Modern C++ Style he refers on slide 28 (a web copy of the slides are here) to this pattern:

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

He says that this is problematic because when calling set_name() with a temporary, noexcept-ness isn't strong (he uses the phrase "noexcept-ish").

Now, I have been using the above pattern pretty heavily in my own recent C++ code mainly because it saves me typing two copies of set_name() every time - yes, I know that can be a bit inefficient by forcing a copy construction every time, but hey I am a lazy typer. However Herb's phrase "This noexcept is problematic" worries me as I don't get the problem here: std::string's move assignment operator is noexcept, as is its destructor, so set_name() above seems to me to be guaranteed noexcept. I do see a potential exception throw by the compiler before set_name() as it prepares the parameter, but I am struggling to see that as problematic.

Later on on slide 32 Herb clearly states the above is an anti-pattern. Can someone explain to me why lest I have been writing bad code by being lazy?

解决方案

Others have covered the noexcept reasoning above.

Herb spent much more time in the talk on the efficiency aspects. The problem isn't with allocations, its with unnecessary deallocations. When you copy one std::string into another the copy routine will reuse the allocated storage of the destination string if there's enough space to hold the data being copied. When doing a move assignment the destination string's existing storage must be deallocated as it takes over the storage from the source string. The "copy and move" idiom forces the deallocation to always occur, even when you don't pass a temporary. This is the source of the horrible performance that is demonstrated later in the talk. His advice was to instead take a const ref and if you determine that you need it have an overload for r-value references. This will give you the best of both worlds: copy into existing storage for non-temporaries avoiding the deallocation and move for temporaries where you're going to pay for a deallocation one way or the other (either the destination deallocates prior to the move or the source deallocates after the copy).

The above doesn't apply to constructors since there's no storage in the member variable to deallocate. This is nice since constructors often take more than one argument and if you need to do const ref/r-value ref overloads for each argument you end up with a combinatorial explosion of constructor overloads.

The question now becomes: how many classes are there that reuse storage like std::string when copying? I'm guessing that std::vector does, but outside of that I'm not sure. I do know that I've never written a class that reuses storage like this, but I have written a lot of classes that contain strings and vectors. Following Herb's advice won't hurt you for classes that don't reuse storage, you'll be copying at first with the copying version of the sink function and if you determine that the copying is too much of a performance hit you'll then make an r-value reference overload to avoid the copy (just as you would for std::string). On the other hand, using "copy-and-move" does have a demonstrated performance hit for std::string and other types that reuse storage, and those types probably see a lot of use in most peoples code. I'm following Herb's advice for now, but need to think through some of this a bit more before I consider the issue totally settled (there's probably a blog post that I don't have time to write lurking in all this).

这篇关于为什么在Herb Sutter的CppCon 2014会话中不推荐使用值设置成员函数(返回基础:现代C ++风格)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆