3
class C
{
public:
    int True(int i) const
    {
        return i+2;
    }
};


const C& F(const C& c)
{
    return c;
}

int main()
{ 
    const C& c = F(C());                             // Line 1
    cout << boolalpha << c.True(1) << endl;          // Line 2
}

问题> 为什么上面的代码可以打印正确的值?我假设该变量在第 2 行时c将引用一个无效的临时对象。C

// 更新

我想更新此 OP 以说明我对这个问题感到担忧的原因。

这是来自 C++ 模板的代码片段:完整指南

// maximum of two values of any type 
template <typename T> 
inline T const& max (T const& a, T const& b) 
{ 
    return a < b ? b : a; 
} 

如您所见,函数返回对传入参数的引用。我只是想知道为什么不使用以下版本:

// maximum of two values of any type 
template <typename T> 
inline T max (T const& a, T const& b) 
{ 
    return a < b ? b : a; 
} 
4

4 回答 4

5

C++11 标准的第 12.2/4 段规定,在某些情况下,临时对象的生命周期确实可以延长到生成它们的完整表达式的末尾:

两种情况下,临时对象在与完整表达式结尾不同的点被销毁。[...]

第一个上下文不相关。但是,根据第 12.2/5 段:

第二个上下文是引用绑定到临时的。引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在,除了

— 临时绑定到构造函数的 ctor-initializer (12.6.2) 中的引用成员,直到构造函数退出。

在函数调用 (5.2.2) 中对引用参数的临时绑定一直持续到包含调用的完整表达式完成为止。

— 在函数返回语句 (6.6.3) 中临时绑定到返回值的生命周期没有延长;临时在 return 语句中的完整表达式的末尾被销毁。

— 在 new-initializer (5.3.4) 中对引用的临时绑定一直存在,直到包含 new-initializer 的完整表达式完成为止。

在这里,临时构造的 withC()绑定到cfunction的参数F。因此,临时在包含对 function 的调用的完整表达式的末尾被销毁F(),并且返回的引用是dangling

在其上调用函数True()会导致Undefined Behavior

于 2013-02-20T20:39:15.873 回答
3

通常,当临时对象绑定到 const 引用时,临时对象的生命周期会延长到引用的生命周期。因此,如果您的代码说const C& c = C(),那么临时文件将尽可能长地c存在。

但是,您将临时传递给另一个函数F()。在这种情况下,C++11 规范的 §12.2.5 规定临时将持续到包含调用的完整表达式完成为止。

因此,当您说 时const C& c = F(C()),临时C()实际上在该语句的末尾被破坏,并且在下一行不再有效。

也就是说,您的代码似乎可以正常运行,因为c.True()在编译时调用是已知的,并且函数定义实际上并未引用 中的任何数据c,因此临时已死的事实并不会真正影响观察到的行为. 然而,这在技术上是未定义的行为。

于 2013-02-20T20:31:51.180 回答
2

这会调用未定义的行为。一旦涉及的完整表达式F(C())完成,临时创建的C()被销毁,因此返回的引用F不再有效。

但是,未定义的行为并不能保证您的程序会崩溃。在更烦人的情况下(像这样),它只会导致微妙的、难以诊断的错误。至于为什么这种未定义的行为会给你这个特定的结果,我建议你参考这个着名的答案

于 2013-02-20T20:39:36.247 回答
1

您所看到的是未定义的行为。很有可能,由于您调用的函数根本不依赖于对象的状态或 vtable,编译器将其内联到cout << boolalpha << ( 1+2 );,因此对象是否被销毁并不重要 - 事实上,编译器可能不会甚至一开始就费心去创造它。

例如,对于 VS2010,在“调试”中它调用FTrue作为静态调用。由于True没有引用this其中的代码,它恰好可以正常工作。(它甚至可能仍然有效,因为没有C可访问的成员变量,所以它唯一能做的就是打印出 的地址this,而那只是堆栈上的一个地址。如果CC的析构函数更改并使用它们的成员变量True,然后您会看到不同之处-在所有情况下,行为都是未定义的,只是实现的产物)

在“发布”中,VS2010 不会费心创建任何C对象或调用F,或者True- 它只是调用cout << boolalpha << 3C::True(2)在编译时确定了 的值。编译器生成的程序中没有临时C对象,无论是否无效。

因此,仅仅因为在对象上调用函数似乎有效,并不意味着该对象存在或曾经存在于编译器生成的程序中。具有不同未定义行为的不同源程序可能会导致编译器生成引发访问冲突的可执行文件,或表现出一些其他行为。


将返回值绑定到 const 引用只适用于按值返回,不返回对参数或本地的引用,否则编译器需要解决停机问题来确定对象的生命周期。

例如,这段代码:

#include<iostream>

class C
{
public:
    int t;
    C( int t ) : t(t){}
    ~C() { std::cout << __FUNCTION__ << " " << t << std::endl; }
};

const C& F(const C& c)
{
    return c;
}
const C& G()
{
    return C(2);
}
C H()
{
    return C(3);
}

int main()
{
    const C& c = F(C(1));
    std::cout << "main 1" << std::endl;
    const C& d = G();    
    std::cout << "main 2" << std::endl;
    const C& e = H();    
    std::cout << "main 3" << std::endl;
}

导致此输出 - 仅H()按值返回,因此只有第 3 个 C 的生命周期延长:

C::~C 1
main 1
C::~C 2
main 2
main 3
C::~C 3
于 2013-02-20T20:47:47.693 回答