返回*作为参考是否安全?

时间:2016-02-27 11:55:39

标签: c++ reference this this-pointer

通常在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;
}

3 个答案:

答案 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引用(但是这个在你的分配和制定者的例子中并不相关。