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

In which cases is it unsafe to (const) reference a return value?

本文关键字:返回值 不安全 引用 情况下 const      更新时间:2023-10-16

我经常使用以下符号:

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/。尽管编译器可能会在这里进行优化,但显式地避免临时对象感觉很好。

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

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

同样,这里的一些数据与dget()无关。

现在我问自己:

  1. 哪些副作用会导致无效?
  2. 将返回值赋给const引用是不好的做法吗?(特别是当我不知道函数的内部)

我的应用程序是单线程的,除了一个Qt gui线程。

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

你不应该。

如果省略失败,这意味着你的编译器是不称职的,或者是在非常不友好的设置下编译。而且您无法在恶意设置或不合格的编译器中生存:不合格的编译器可以将带整数的a+=b转换为for (int i = 0; i < abs(b); ++i) {if (b>0) ++a; else --a;},并且丝毫不违反标准。

省略是一种核心语言特性。不要仅仅因为你不相信它会发生就写出糟糕的代码。

当您需要对函数提供的数据的引用而不是数据的独立稳定副本时,您应该通过引用捕获。如果你不了解函数的返回值的生命周期,那么通过引用进行捕获就是不安全的。

即使你知道数据是稳定的,花在维护代码上的时间比写代码的时间要多:读你代码的人必须一眼就能看出你的假设是成立的。非局部bug是不好的:对你调用的函数的一个看似无害的改变不应该破坏你的代码。


最终的结果是,除非你有很好的理由不这样做,否则按值取值。

按值取值使你和编译器更容易对你的代码进行推理。它增加了局部性

当你有很好的理由不这样做时,就参考一下。

根据上下文,这个好的理由可能不需要非常有力。但是它不应该基于一个不称职的编译器的假设。

应避免过早悲观,但也应过早优化。当您发现了性能问题时,应该使用(或存储)引用而不是值。编写简洁易懂的代码。将复杂性塞进紧密编写的类型中,并使外部接口干净而简单。按值取东西,因为值可以解耦状态。


优化是可替代的。通过简化更多的代码,您可以使其更容易使用(并且更高效)。然后,当您确定性能重要的部分时,您可以在上花费精力使代码更快。一个典型的例子是基础代码:如果在编写基础代码时没有考虑到性能的易用性,那么基础代码(到处都在使用)很快就会拖累性能。在这种情况下,您希望将复杂性隐藏在类型中,并公开一个简单易用的外部接口,该接口不需要用户理解其内部结构。

但是代码在一个随机的函数某处?使用值,这是最容易使用的容器,具有最友好的o符号,对最昂贵的操作和最简单的接口。载体是合理的(避免过早的呼吸),但不要担心一些地图。

找出1%-10%的代码占用了90%-99%的时间,并使其快速。如果代码的其余部分具有良好的o符号性能(因此在使用比您测试的数据集更大的数据集时,它不会变得惊人地慢),那么您将处于良好状态。然后开始用荒谬的数据集测试,然后找到慢的部分。

哪些副作用会导致无效?

持有类的内部状态的引用(即,与扩展临时对象的生命周期相比),然后调用任何非const成员函数可能会使引用失效。

将返回值赋给const引用是不好的做法吗?(特别是当我不知道函数的内部时)

我会说坏做法是保持对类实例的内部状态的引用,改变类实例,并继续使用原始引用(除非文档中说明非const函数不会使引用无效)

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

  1. 哪些副作用会导致无效?

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

  • 将返回值赋给const引用是不好的做法吗?(特别是当我不知道函数的内部时)

  • 一样。如果按照设计,您不必修改获得的对象(通过引用或通过值),那么最好将其定义为const。一旦您将某些内容定义为const,编译器将确保您不会试图在代码中以某种方式修改它。

    这是知道你的函数返回什么的问题。您还应该完全理解返回值类型及其语义。