在哪些情况下(const)引用返回值是不安全的?

时间:2015-08-19 20:15:21

标签: c++

我倾向于使用以下符号:

const Datum& d = a.calc();
// continue to use d

当calc的结果在堆栈上时,这是有效的,请参阅http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/。尽管编译器可能在这里进行优化,但明确避免使用临时对象会感觉很好。

今天我意识到,在将数据写入d的成员后,a的内容无效。在这种特殊情况下,get函数只是返回对另一个成员的引用,这与写入完全无关。像这样:

const Datum& d = a.get();
// ... some operation ...
a.somedata = Datum2();
// now d is invalid.

同样,somedata与dget()无关。

现在我问自己:

  1. 哪些副作用可能导致失效?
  2. 将返回值分配给const引用是不是很糟糕? (特别是当我不知道功能的内部时)
  3. 我的应用程序是单线程的,除了Qt GUI-Thread。

4 个答案:

答案 0 :(得分:6)

你似乎害怕失败。当auto x = some_func();返回临时对象时,auto&& x = some_func();会在some_func()上产生额外的移动构造。

你不应该。

如果省略失败,则意味着您的编译器无能力,或者使用坦率的恶意设置进行编译。并且你无法在恶意设置或不称职的编译器中存活:不称职的编译器可以将a+=b整数转换为for (int i = 0; i < abs(b); ++i) {if (b>0) ++a; else --a;}并且不违反标准的任何一个。

Elision是一种核心语言功能。不要因为你不相信会发生错误而编写错误的代码。

当您想要引用函数提供的数据时,应该通过引用进行捕获,而不是单独稳定的数据副本。如果您不了解函数的返回值的生命周期,则通过引用捕获只是不安全

即使您知道数据会保持稳定,维护代码所花费的时间也比编写代码要多:阅读代码的人必须能够一目了然地看到您的假设成立。并且非本地错误很糟糕:对您调用的函数进行看似无害的更改不应该破坏您的代码。

最终的结果是,除非你有充分的理由不去,否则按值计算。

按值进行操作会使您和编译器更容易推理您的代码。它增加了地方。

如果你有充分的理由不这样做,那么可以参考一下。

根据具体情况,这个好理由可能不一定非常强大。但它不应该基于一个无能的编译器的假设。

应该避免过早的悲观,但应该过早优化。获取(或存储)引用而不是值应该是您在确定性能问题时所做的事情。编写干净,易于理解的代码。将复杂性推向紧密编写的类型,并使外部界面简洁明了。按值计算,因为值会使状态解耦。

优化是可替代的。通过使更多代码更简单,您可以更轻松地使用(并提高工作效率)。然后,当您确定性能重要的部分时,您可以花费那里来使代码更快。

一个很好的例子是基础代码:如果没有以性能易用性编写,基础代码(在任何地方使用)很快就会成为性能的一般拖累。在这种情况下,您希望隐藏内部类型的复杂性,并展示一个简单易用的外部界面,并不要求用户理解这些内容。

但代码在某个随机函数中?使用值,最容易使用容器和最友好的O-notation来执行最昂贵的操作以及最简单的界面。矢量如果合理(避免过早的悲观),但不要冒出几张地图。

找到占用90%-99%的时间的代码的1%-10%,并快速完成。如果你的代码的其余部分具有良好的O符号表现(因此,对于比你测试更大的数据集,它不会变得令人震惊地变慢),你将会处于良好的状态。然后开始用荒谬的数据集进行测试,然后找到缓慢的部分。

答案 1 :(得分:5)

  

哪些副作用可能导致失效?

保持对类的内部状态的引用(即,延长临时的生命周期),然后调用任何非const成员函数可能会使引用无效。

  

将返回值分配给const引用是不是很糟糕? (特别是当我不知道功能的内部时)

我会说糟糕的做法是持有对类实例的内部状态的引用,改变类实例,并继续使用原始引用(除非记录为非-const函数不会使引用无效

答案 2 :(得分:0)

我不确定我是否正在回答您的预期,但...... const关键字与“不安全”关键字无关。这里。 即使您返回非const引用,它也可能变为无效。 const表示不允许修改它。例如,如果您的get()返回const成员,或get()本身被定义为const,就像const some_refetence_t& get() const { return m_class_member; }一样。 现在关于你的问题:

  1.   

    哪些副作用可能导致失效?

  2. 如果原始值发生变化,可能会有许多副作用。例如,假设返回的值是对堆上被删除的对象的引用...或者在原始值获得更新时缓存返回的值。所有这些都是设计问题。如果按照设计可以进行这样的情况,那么返回值应该是值(并且在缓存的情况下,它不能被缓存!:))。

    1.   

      将返回值分配给const引用是不是很糟糕?   (特别是当我不知道函数的内部时)

    2. 同样的事情。如果按照设计,您不必修改您获得的对象(通过引用或按值),而不是最好将其定义为const。一旦将某些内容定义为const,编译器将确保您不会尝试在代码中以某种方式对其进行修改。

答案 3 :(得分:-1)

这是一个知道你的功能返回的问题。您还应该完全了解返回值类型及其语义。