我试图更好地理解如何将左值和右值作为引用处理,所以我创建了这个玩具示例:
#include <iostream>
struct Val
{
Val(int num) : num(num){};
~Val()
{
std::cout << "Destructing with value " << num << std::endl;
}
int num;
};
const Val &test(const Val &val)
{
return val;
}
int main()
{
std::cout<< "Creating foo with value 5" <<std::endl;
const Val &foo = test(Val(5));
std::cout<< "Creating bar with value 3" <<std::endl;
const Val &bar(3);
std::cout<< "Finishing main function" <<std::endl;
return 0;
}
打印出:
Creating foo with value 5
Destructing with value 5
Creating bar with value 3
Finishing main function
Destructing with value 3
本质上,我们看到这个右值
Val(5)
绑定到函数val
中的常量引用参数test
,并且返回相同的值 - 但是,析构函数会立即被调用,因为它是临时的。但是当我们尝试构造 Val(3)
并分配给 const 引用时,它仍然在整个块的范围内。
我的想法是,我们可以将右值绑定到常量引用,这将延长它们的生命周期,直到该引用超出范围,但这里似乎不一定是这种情况。如果您能深入了解我的误解,我将不胜感激。
给定
const Val &foo = test(Val(5));
,临时的Val(5)
将在完整表达后立即销毁,其生命周期不会延长到参考foo
的生命周期。它不是直接绑定到foo
,而是绑定到test
的参考参数。
在参考初始化,
(强调我的)
每当引用绑定到临时对象或子对象时 其中,临时的寿命延长以匹配 引用的生命周期,但以下情况除外:
- 在函数调用中绑定到引用参数的临时绑定存在,直到包含该函数调用的完整表达式结束为止:if 该函数返回一个引用,该引用比完整表达式的寿命长, 它变成了一个悬空的参考。
一般来说,临时的生命周期不能通过以下方式进一步延长: “传递它”:第二个引用,从引用初始化 临时绑定的,不影响其生命周期。
当您向 test() 本身添加输出时
const Val &test(const Val &val)
{
std::cout << "test with value " << val.num << '\n';
return val;
}
您会看到,临时对象一直存在到函数结束,但不会超出
Creating foo with value 5
test with value 5
Destructing with value 5
Creating bar with value 3
Finishing main function
Destructing with value 3
我的想法是,我们可以将右值绑定到常量引用,这将延长它们的生命周期,直到该引用超出范围
是的,确实
val
参数确实延长了Val(5)
的生命周期,但是当
test
返回时,
val
本身就被销毁了,没有任何东西可以让
Val(5)
继续存活,所以它也被销毁了。通过引用返回,您实际上返回了一个悬空引用,因此您有未定义的行为:
参数val
是对实际参数
Val(5)
的引用这一事实并不影响
val
是
test
返回后不再可用,并且您正在通过引用返回(好吧,尝试返回)it(
val
),而不是其引用的实体。