【问题标题】:C++ return reference to temporaries or store them in objectsC++ 返回对临时对象的引用或将它们存储在对象中
【发布时间】:2012-09-23 11:19:29
【问题描述】:

考虑以下处理 const 引用的代码:

const int & func (const int &x)
{
    return x;
}

struct Foo {
    Foo (const int &x)
    : m_x(x) {}

    const int & getX ()
    { return m_x; }

    const int &m_x;
};

我想知道现在允许以下哪些(如果有):

int x = func(int(7));
int y = Foo(int(7)).getX();

是否可以保证临时int 对象在被赋值或getX 使用之前仍然存在?

更新:看来这是安全的 - 但究竟为什么呢?

  1. 是因为临时对象以递归方式绑定到 const 引用,并且只要对它们的绑定引用存在就保证存在?
  2. 还是因为它们在完整表达式的持续时间内保证存在?

考虑存储指针而不是引用的边缘情况:

struct Foo {
    Foo (const int &x)
    : m_x(&x) {}

    const int & getX ()
    { return *m_x; }

    const int *m_x;
};

int y = Foo(int(7)).getX();

如果情况 1) 是正确的,这似乎是行不通的。但如果情况 2) 是正确的,它会。

【问题讨论】:

  • 这两个选项都不安全,临时变量只保证在创建它们的语句期间存在。
  • @DimaRudnik 所以?这使它们安全,因为它们被复制到 x 和 y。

标签: c++ reference return temporary


【解决方案1】:

两者都是安全的,因为您将值复制到 x 和 y。临时变量在完整表达式结束之前有效。

12.2 临时对象

4) 临时对象在两种情况下被销毁 与完整表达式的结尾不同的点。第一个上下文 是调用默认构造函数来初始化 大批。如果构造函数有一个或多个默认参数,任何 在默认参数表达式中创建的临时对象被销毁 从构造函数返回后立即。

5) 第二个上下文是 当一个引用绑定到一个临时的。临时的 引用被绑定或者是一个完整对象的临时对象 引用绑定到的子对象在 除以下指定外,参考。临时绑定到 构造函数的 ctor-initializer (12.6.2) 中的引用成员仍然存在 直到构造函数退出。临时绑定到引用 函数调用(5.2.2)中的参数一直持续到完成 包含调用的完整表达式。临时绑定到 函数返回语句 (6.6.3) 中的返回值一直持续到 函数退出。在所有这些情况下,在 初始化引用的表达式的评估,除了 引用绑定的临时对象,在 创建它们的完整表达式的结尾以及 倒序完成他们的建设。如果这辈子 两个或多个绑定引用的临时对象在 同一点,这些临时对象在那个点被摧毁 倒序完成他们的建设。此外, 与引用绑定的临时对象的销毁应考虑到 使用静态或 自动存储时间(3.7.1, 3.7.2);也就是说,如果 obj1 是在创建临时对象之前创建的具有静态或自动存储持续时间的对象,则临时对象应 在 obj1 被销毁之前被销毁;如果 obj2 是一个对象 临时存储后创建的静态或自动存储持续时间 创建后,obj2 销毁后,临时对象将被销毁。 [ 示例:

class C 
{ 
/ / ... 
public : 
    C(); 
    C(int ); 
    friend C operator +(const C&, const C&); 
    ~C(); 
}; 
C obj1 ; 
const C& cr = C (16)+ C (23); 
C obj2 ; 

表达式 C(16)+C(23) 创建了三个临时变量。第一个临时T1 保存表达式 C(16) 的结果,第二个临时 T2 到 保存表达式 C(23) 的结果,以及第三个临时 T3 到 保存这两个表达式相加的结果。这 然后将临时 T3 绑定到参考 cr。未指定 是先创建 T1 还是 T2。在 T1 是的实现上 在 T2 之前创建,保证 T2 在 T1 之前销毁。 临时 T1 和 T2 绑定到参考参数 运算符+;这些临时对象在完整结束时被销毁 包含对 operator+ 的调用的表达式。临时T3必然 引用 cr 在 cr 的生命周期结束时被销毁,也就是说,在 程序结束。此外,T3 的顺序是 destroy 考虑了其他对象的销毁顺序 具有静态存储持续时间。也就是因为构造了obj1 在 T3 之前,并且 T3 在 obj2 之前构造,保证 obj2 在 T3 之前销毁,而 T3 在 obj1 之前销毁。 ——结束示例]

【讨论】:

  • 能否引用C++标准的相关部分?
  • “在函数调用 (5.2.2) 中临时绑定到引用参数会一直持续到包含调用的完整表达式完成为止。”我认为意味着我的第一个案例“func(int(7));”是安全的。
  • 但是第二段中的 3 个例外不包括临时绑定到构造函数的引用参数,因此本段的第一部分适用:“引用绑定到的临时.. . 将在引用的整个生命周期内持续存在,但以下指定除外”。这似乎表明在“Foo(int(7)).getX();”中,临时 int(7) 在 Foo() 构造函数的引用参数超出范围后被销毁。
  • @AmbrozBizjak 它在完整表达式结束后被销毁。 getX 是表达式的一部分。两者都很安全。
  • 你读过我说的吗? “引用绑定到的临时对象......在引用的生命周期内持续存在,除了......”之后的 3 个异常中的哪一个适用于将临时对象绑定到构造函数调用的引用参数?还是这里隐含的构造函数是函数调用?
【解决方案2】:

两者都是安全的。临时对象可以绑定到const 引用,并且它们会持续到绑定临时对象的表达式被执行。在这种情况下,您将它绑定到构造函数参数,并且它一直存在到构造函数的右大括号。

类似的现象是使用临时函数作为 const 引用的默认函数参数:

void foo(const someclass& bla = someclass()); // bind const ref with default constructed someclass

【讨论】:

  • 并非如此。对于构造函数,它们一直持续到构造函数的末尾,尽管成员引用还没有超出范围。在这种特殊情况下没关系,因为它是一个临时对象,但不是一般情况。
  • @Luchian 和其他所有人:完全清楚;我在这里只谈论临时人员的生命周期,而不是您能想出的任何其他混乱的悬空参考代码。
  • 这里也一样... ideone.com/f9dAu 那是临时的。它的生命周期结束了,尽管它绑定的 const 引用仍在范围内。而且它没有搞砸悬空的参考代码。
  • 你不能只是制定一个规则然后说——“这总是适用,除非你写了一些乱七八糟的代码,而实际上它不适用”。
  • @rubenvb 您发布的 ideone 链接中的代码调用了未定义的行为,因为临时 42 int 对象在第 11 行末尾被销毁。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多