临时对象被销毁后为什么不崩溃

时间:2013-02-20 20:22:07

标签: c++

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
}

问题&GT;为什么上面的代码可以打印正确的值? 我假设变量c在遇到第2行时会引用无效的临时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 个答案:

答案 0 :(得分:5)

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

  

两个上下文,其中temporaries在与完整表达式结尾不同的点被销毁。 [...]

第一个背景不相关。但是,根据第12.2 / 5段:

  

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

     

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

     

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

     

- 函数返回语句(6.6.3)中返回值临时绑定的生命周期未扩展;临时在return语句中的full-expression结束时被销毁。

     

- 在new-initializer(5.3.4)中对引用的临时绑定一直持续到包含new-initializer的full-expression完成为止。

此处,使用C()构造的临时值绑定到函数c的参数F。因此,临时在full-expression的末尾被销毁,其中包含对函数F()的调用,返回的引用是悬空

在其上调用函数True()会导致未定义的行为

答案 1 :(得分:3)

通常,当临时绑定到const引用时,临时的生命周期会延长到引用的生命周期。因此,如果您的代码说const C& c = C(),那么只要c,临时就会存在。

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

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

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

答案 2 :(得分:2)

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

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

答案 3 :(得分:1)

您所看到的是未定义的行为。很可能,因为你调用的函数根本不依赖于对象的状态或vtable,编译器将它内联到cout << boolalpha << ( 1+2 );,所以对象是否被销毁无关紧要 - 事实上,编译器可能一开始并不打算创建它。

例如,对于VS2010,在“Debug”中,它将FTrue称为静态调用。由于True未引用this,因此其中的代码恰好可以正常工作。 (它甚至可能仍然有用,因为C中没有成员变量可以访问,所以它唯一能做的就是打印出this的地址,那就是只是堆栈上的一个地址。如果C的成员变量被C的析构函数改变而True使用它们,那么你会看到差异 - 在所有情况下,行为是未定义的,只是实现的一个人工制品)

在“发布”中,VS2010无需创建任何C个对象或致电FTrue - 只需调用cout << boolalpha << 3,即可确定C::True(2)的值1}}在编译时。在编译器生成的程序中没有临时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