在询问this question时,我学会了对临时对象的const引用在C ++中是有效的:
int main ()
{
int a = 21;
int b = 21;
//error: invalid initialization of non-const reference
//int & sum = a + b;e [...]
//OK
int const & sum = a + b;
return sum;
}
但是在下面的示例中,const引用refnop
引用了一个被销毁的临时对象。我想知道为什么?
#include <string>
#include <map>
struct A
{
// data
std::map <std::string, std::string> m;
// functions
const A& nothing() const { return *this; }
void init() { m["aa"] = "bb"; }
bool operator!= (A const& a) const { return a.m != m; }
};
int main()
{
A a;
a.init();
A const& ref = A(a);
A const& refnop = A(a).nothing();
int ret = 0;
if (a != ref) ret += 2;
if (a != refnop) ret += 4;
return ret;
}
使用GCC 4.1.2和MSVC 2010进行测试,返回4;
$> g++ -g refnop.cpp
$> ./a.out ; echo $?
4
ref
和refnop
之间的差异是对nothing()
的调用,它实际上什么也没做。看来这个调用之后,临时对象就被破坏了!
我的问题:
为什么在refnop
的情况下,临时对象的生命周期与其const引用不同?
答案 0 :(得分:10)
当临时对象绑定到第一个引用时,临时对象的生存期扩展只能执行一次。之后,引用引用临时对象的知识消失了,因此无法进一步扩展生命周期。
令你困惑的案例
A const& refnop = A(a).nothing();
与此案例类似:
A const& foo(A const& bar)
{
return bar;
}
//...
A const& broken = foo(A());
在这两种情况下,临时绑定到函数参数(this
的隐式nothing()
,bar
的{{1}}并将其生命周期'扩展'到函数参数的生命周期。我把'扩展'放在引号中,因为临时的自然寿命已经更长了,所以没有实际的扩展。
因为生命周期扩展属性是不可传递的,所以返回引用(恰好引用临时对象)将不会进一步延长临时对象的生命周期,结果是foo()
和{{ 1}}最终引用不再存在的对象。
答案 1 :(得分:1)
我原来的例子很复杂。
因此,我在这里发布一个更简单的例子,并提供相应的ISO C++ standard段。
这个更简单的例子也可以在coliru.stacked-crooked.com/
上找到#include <iostream>
struct A
{
A(int i) { std::cout<<"Cstr "<< i<<'\n'; p = new int(i); }
~A() { std::cout<<"Dstr "<<*p<<'\n'; delete p; }
const A& thiz() const { return *this; }
int *p;
};
const A& constref( const A& a )
{
return a;
}
int main()
{
const A& a4 = A(4);
const A& a5 = A(5).thiz();
const A& a6 = constref( A(6) );
std::cout << "a4 = "<< *a4.p <<'\n';
std::cout << "a5 = "<< *a5.p <<'\n';
std::cout << "a6 = "<< *a6.p <<'\n';
}
使用命令行g++-4.8 -std=c++11 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
输出:
Cstr 4
Cstr 5
Dstr 5
Cstr 6
Dstr 6
a4 = 4
a5 = 0
a6 = 0
Dstr 4
如您所见,a5
和a6
引用的临时对象分别在函数thiz
和constref
的末尾被破坏。
这是§12.2临时对象的摘录,其中粗体部分适用于这种情况:
第二个上下文是引用绑定到临时的。 引用绑定的临时或临时 这是参考的子对象的完整对象 除了以下内容之外,绑定的持续时间仍然存在:
- 临时绑定到构造函数中的引用成员 ctor-initializer(12.6.2)一直存在,直到构造函数退出。
- 临时绑定到函数调用中的引用参数(5.2.2) 持续到包含调用的完整表达式完成为止。
- 临时绑定到返回值的生命周期 函数返回语句(6.6.3)未扩展;暂时的 在return语句中的full-expression结尾处被销毁。
- new-initializer (5.3.4)中与引用的临时绑定仍然存在 直到包含 new-initializer 的完整表达式完成。
这是一个更完整的例子:
#include <iostream>
struct A
{
A() { std::cout<<"Cstr 9\n"; p = new int(v = 9); }
A(int i) { std::cout<<"Cstr "<<i<<'\n'; p = new int(v = i); }
A(const A&o){ std::cout<<"Copy "<<o.v<<'\n'; p = new int(v = 10+o.v); }
~A() { std::cout<<"Del "<<v<<' '<<*p<<'\n'; *p = 88; delete p; }
const A& thiz() const { return *this; }
int *p;
int v;
};
const A& constref( const A& a )
{
return a;
}
std::ostream& operator<<( std::ostream& os, const A& a )
{
os <<"{ *p="<< *a.p <<" , v="<< a.v <<" }\n";
return os;
}
int main()
{
std::cout << "---const A a1 = A(1)" "\n";
const A a1 = A(1);
std::cout << "---const A a2 = A(2).thiz()" "\n";
const A a2 = A(2).thiz();
std::cout << "---const A a3 = constref( A(3) )" "\n";
const A a3 = constref( A(3) );
std::cout << "---const A& a4 = A(4)" "\n";
const A& a4 = A(4);
std::cout << "---const A& a5 = A(5).thiz()" "\n";
const A& a5 = A(5).thiz();
std::cout << "---const A& a6 = constref( A(6) )" "\n";
const A& a6 = constref( A(6) );
std::cout << "a1 = "<< a1;
std::cout << "a2 = "<< a2;
std::cout << "a3 = "<< a3;
std::cout << "a4 = "<< a4;
std::cout << "a5 = "<< a5;
std::cout << "a6 = "<< a6;
}
使用相同的g++
命令行输出相应的输出:
---const A a1 = A(1)
Cstr 1
---const A a2 = A(2).thiz()
Cstr 2
Copy 2
Del 2 2
---const A a3 = constref( A(3) )
Cstr 3
Copy 3
Del 3 3
---const A& a4 = A(4)
Cstr 4
---const A& a5 = A(5).thiz()
Cstr 5
Del 5 5
---const A& a6 = constref( A(6) )
Cstr 6
Del 6 6
a1 = { *p=1 , v=1 }
a2 = { *p=12 , v=12 }
a3 = { *p=13 , v=13 }
a4 = { *p=4 , v=4 }
a5 = { *p=0 , v=5 }
a6 = { *p=0 , v=6 }
Del 4 4
Del 13 13
Del 12 12
Del 1 1