使用行为不同的引用返回TMP

returning tmp using references behaving differently

本文关键字:引用 返回 TMP      更新时间:2023-10-16

我知道使用引用返回临时变量不起作用,因为在函数终止后临时对象丢失了,但是下面的代码可以工作,因为返回的临时对象被赋值给了另一个对象。

我假设临时对象在函数调用行之后被销毁。如果是这样,为什么这种方法链不能工作呢?

Counter& Counter::doubler()
{ 
   Counter tmp;
   tmp.i = this->i * 2;
   return tmp;
}
int main()
{
    Counter d(2);
    Counter d1, d2;
    d1 = d.doubler();                            // normal function call
    std::cout << "d1=" << d1.get() << std::endl; // Output : d1=4 
    d2 = d.doubler().doubler();                  // Method chaining
    std::cout << "d2=" << d2.get() << std::endl; // Output : d2=0 
    return 0;
}

如果函数返回对局部对象的引用,则该对象将在函数返回时立即销毁(就像局部对象一样)。持续到函数调用的行末。

在对象被销毁后访问它将产生不可预测的结果。对于"工作"的某些定义,有时它可能有效,有时则可能无效。别这么做

Counter& doubler()
{ 
Counter tmp;
tmp.i=this->i*2;
return tmp;
}

这是未定义的行为。从函数返回后,你的引用将是悬空的,因为Counter析构函数将被局部对象tmp调用。

真正的问题不是"为什么这种方法链不工作?",而是"为什么第一个('正常')函数调用工作?"

答案是没有办法知道,因为它可能会破坏你的程序。

说明清楚:通过引用返回临时对象是未定义的行为。当然,这意味着它可能今天碰巧起作用,明天就不起作用了。

当函数返回并发生堆栈回滚时,这是逻辑回滚,堆栈指针被设置为不同的值。如果函数返回一个局部变量引用,那么指向local的内存位置可能仍然在进程中,并且设置了相同的位。然而,这并不能保证,在几次调用之后将无效,并可能导致未定义的行为。

其他的都是可以的,只是"不要摆弄本地对象的引用"

至于为什么它在一种情况下起作用而在其他情况下不起作用

  1. 当你单独调用它时,当函数返回时,对象仍然在堆栈上。授予一个"销毁"的对象-但是无论该对象占用的空间如何,它仍然存在于堆栈中。如果你有一个简单的对象,比如只有一个int成员,那么它在堆栈上没有任何干扰,除非你在堆栈上分配了其他东西,或者析构函数决定做一个非常彻底的工作并删除一个整数成员(大多数析构函数都不会这样做)。当然啦,但是直到下一行之前都不会发生什么事情会把它从堆栈中移走。您的引用指向一个有效的内存位置,并且您的(销毁)对象将在那里。这就是为什么它对你有效。

  2. 当你链式调用它时,看到第一个调用返回一个对栈上tmp的引用。正如上面第1条所解释的,到目前为止没有问题。您的(已销毁的)tmp仍然在堆栈上。但请注意你调用第二个加倍器的时刻。第二个双倍器函数调用中的tmp在哪里?就在你第一次调用tmp的地方!!第二个调用用一个值为0的tmp(默认构造的tmp)覆盖对象(值为4的tmp)。第二个调用实际上是在一个值为0的Counter上进行的,因此您得到0。非常棘手——这就是为什么要忘记返回对局部变量的引用。

现在纯粹主义者可能会尖叫-没有定义,不,不,只是不要这样做-我和他们一样-我自己说过两次(现在是三次)不要这样做。但人们可能会尝试。我打赌像下面这样的"简单"对象,和问题中的代码完全一样(以便没有任何东西干扰堆栈),每个人都会得到一致的4,0 -没有随机性,没有未定义....

class Counter
{
public:
    Counter()
    {
        i = 0;
    }
    Counter(int k)
    {
        i = k;
    }
    int get()
    {
        return i;
    }
    int i;
    Counter& doubler();
};