通常在assignment operator overloading中使用返回对此对象的引用。它也被用作named parameters idiom的基础,它允许通过对setter方法的调用链来初始化对象:Params().SetX(1).SetY(1)
每个都返回对* this的引用。
但是返回对*this
的引用是否正确。如果我们调用该方法为临时对象返回对此的引用,该怎么办:
#include <iostream>
class Obj
{
public:
Obj(int n): member(n) {}
Obj& Me() { return *this; }
int member;
};
Obj MakeObj(int n)
{
return Obj(n);
}
int main()
{
// Are the following constructions are correct:
std::cout << MakeObj(1).Me().member << std::endl;
std::cout << Obj(2).Me().member << std::endl;
Obj(3).Me() = Obj(4);
return 0;
}
答案 0 :(得分:7)
是的,返回*这是安全的。容易的情况是,这不是暂时的,尽管即使这样,这应该是可能的:
临时对象作为评估全表达式(1.9)的最后一步被销毁,该表达式(词法上)包含创建它们的点。即使该评估以抛出异常结束,也是如此(C ++03§12.2/ 3)。
换句话说,在你到达分号之前,一切都应该没问题(理论上)。
以下代码应该有效:
const Obj &MakeMeObj(int n) { return Obj(n).Me(); }
std::cout << MakeMeObj(1).member << std::endl;
虽然这不起作用:
Obj &Me() & { return *this; }
Obj &Me() && = delete;
这是合乎逻辑的,因为您返回对临时的引用。大多数编译器都会对此发出警告/错误,但如果您的代码变得复杂,则需要注意这一点。
就个人而言,我会阻止在临时对象上调用这些方法来强制API用户考虑对象的生命周期。可以通过重载方法来完成:(如果您的编译器已经支持它)
{{1}}
答案 1 :(得分:4)
// Are the following constructions are correct: std::cout << MakeObj(1).Me().member << std::endl; std::cout << Obj(2).Me().member << std::endl;
是的,因为在每一行中,所有临时对象的生命周期都被扩展为考虑完整表达式。
正如cppreference.com所说:
(...)所有临时对象都作为最后一步被销毁 评估(词法上)包含该点的完整表达式 它们的创建地点(...)。
如果您尝试拆分完整表达式,那么您(希望)会收到编译器错误或警告:
// not allowed:
Obj& ref = MakeObj(1);
std::cout << ref.Me().member << std::endl;
在其他情况下,编译器可能不够智能以查看问题,创建可执行文件而不提供任何诊断消息,并最终在程序中构建未定义的行为:
// undefined behaviour:
Obj &ref = MakeObj(1).Me();
std::cout << ref.member << std::endl;
答案 2 :(得分:3)
是的,这是安全的。临时对象的生命将一直持续到语句结束(更确切地说是对其创建的完整表达式的评估)。这由标准保证:
12.2 / 3:临时对象被销毁,作为评估(词法)包含的完整表达式的最后一步 他们被创造的地方。
如果绑定到引用,甚至可以在某些条件下延长临时生命周期。但不要指望奇迹在这里。试图将引用保留在语句之外(f.ex通过获取地址或分配引用)可以快速导致UB(demo)。
如果您在const
个对象上使用此类构造,那么您也会遇到一些麻烦,因为您尝试返回非const
引用(但是这个在你的分配和制定者的例子中并不相关。