为什么在C++中可以通过引用返回局部变量的函数,但不能返回临时变量的函数?

5
例如,此函数f定义如下:

int f(int x){return x;}

正如您所知,您无法将引用分配给此临时整数:

int& rf=f(2);// this will give an error

但是如果我像这样重新定义我的函数f:
int& f(int x){return x;}
f(2);// so now f(2) is a reference of x, which has been destroyed 

所以我的问题是:编译器如何不允许你创建一个对语句后将被销毁的临时变量的引用(在第一种情况下),而另一方面,它允许你创建一个对x的引用f(2),尽管编译器知道这个变量会在return之后被销毁。

1
这是未定义行为(访问引用的方式),任何明智的编译器都会警告你。 - Cat Plus Plus
6个回答

7

返回指向本地变量的引用可能很难或不可能被编译器检测到。例如:

int & f()
{
    int x;
    extern int & g(int & x);

    // Does this return a reference to "x"?
    // The compiler has no way to tell.
    return g(x);
}

即使不调用外部函数,分析复杂的程序流以确定返回的引用是否为本地引用仍然很困难;与其试图定义什么算是“足够简单”来进行诊断,标准并不要求进行诊断-它只声明会产生未定义的行为。至少在简单情况下,一个好的编译器应该发出警告。

将临时对象绑定到非常量引用是编译器可以轻松检测到的,因此标准要求对此进行诊断。


1

因为根据标准规定,从函数返回对临时变量的引用是未定义行为。

实际上出现问题的是函数定义:

int& f(int x)
{
   return x;
}

1

你可以将临时rvalue绑定到const引用上,以延长它的生命周期。

const int& rf=f(2); 

1

为了拒绝您的第一个代码片段,编译器应用了一个简单的规则,即您不能直接将临时对象绑定到非 const 引用。

为了拒绝第二个代码片段,编译器可以应用一个规则,即返回引用的函数的 return 语句不能是自动变量的名称(包括按值传递的函数参数)。这对我来说似乎是一个相当简单的规则。

我不知道为什么标准没有指定这样做是不合法的。我想不出任何有效的用途,但也许在第一个标准的时候,这会给某些实现带来过多的负担。或者可能认为这只是半个修复,不值得费心(还有很多其他方法可以创建悬空引用,这只是阻止其中之一)。

标准之所以通常不阻止您创建绑定到临时对象的非 const 引用,是因为有时候这样做是可以的。例如:

struct Foo {
    static void set(Foo &f) { f.val = 0; }
    int val;
    int bar() {
        set(*this);
        return val;
    }
};

std::cout << Foo().bar() << "\n";

这里的Foo()是一个临时对象,而set(*this)这一行将其绑定到了一个非const引用(但不是直接绑定,它使用了一个左值表达式*this,有时候指向一个临时对象,有时候则不是)。这里没有问题,临时对象的生命周期超过了引用的生命周期。因此,如果语言要防止任何临时对象被绑定到非const引用,那就会过于限制。


0

除非你确定返回的引用仍将指向有效的内容,否则通过引用返回不是一个好主意。

否则,会出现未定义的行为。

由于变量是局部的,所以可以确定引用无效。


2
由于变量是局部的,您可以确定它是无效的。 - Luchian Grigore

0
问题在于,从语义上讲,在堆栈上的变量和在堆上的变量等之间没有区别。因此,即使这是未定义的行为,语言也不得不允许这种情况发生。 在第一个示例中,您会收到一个简单的编译错误,因为您正在尝试将引用绑定到临时变量上,而语言可能会禁止这样做。

是的,我理解了,但为什么第二个例子可以工作?你为什么提到堆,而我没有提到堆呢? - AlexDan
@AbdessamadBond,你的第二个例子为什么不起作用,这就是答案。我提到堆,因为返回指向堆上变量的引用是可以的(即使这可能是不好的风格)。从语义上讲,在堆和栈上的变量之间没有区别,而在变量和临时值之间有区别。 - cooky451

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接