【问题标题】:const reference to a temporary object becomes broken after function scope (life time)对临时对象的 const 引用在函数范围(生命周期)之后被破坏
【发布时间】:2013-06-18 15:07:24
【问题描述】:

在询问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

refrefnop 之间的区别在于对 nothing() 的调用实际上什么都不做。似乎在这个调用之后,临时对象被销毁了!

我的问题:
为什么在refnop的情况下,临时对象的生命周期与其const引用不一样?

【问题讨论】:

  • 注意:使用 g++ 4.4 和 4.6 版本,这个 sn-p 返回 0...

标签: c++ reference scope temporary-objects const-reference


【解决方案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 &amp;&amp; ./a.out的输出:

Cstr 4
Cstr 5
Dstr 5
Cstr 6
Dstr 6
a4 = 4
a5 = 0
a6 = 0
Dstr 4

如您所见,a5a6 引用的临时对象分别在函数 thizconstref 的末尾被破坏。

这是§12.2 临时对象的摘录,其中粗体部分适用于这种情况:

第二个上下文是引用绑定到临时的。 引用绑定到的临时或临时 这是引用的子对象的完整对象 is bound 在引用的生命周期内持续存在,除了:

  • 临时绑定到构造函数的引用成员 ctor-initializer (12.6.2) 一直持续到构造函数退出。
  • 在函数调用中临时绑定到引用参数 (5.2.2) 一直持续到包含调用的完整表达式完成为止。
  • 临时绑定到返回值的生命周期 函数返回语句 (6.6.3) 未扩展;暂时的 在 return 语句中的完整表达式的末尾被销毁。
  • 临时绑定到 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

【讨论】:

    【解决方案2】:

    当临时对象绑定到第一个引用时,临时对象的生命周期扩展只能执行一次。之后,引用指向临时对象的知识就消失了,因此无法再延长生命周期。

    让你困惑的案例

    A const& refnop = A(a).nothing();
    

    类似这种情况:

    A const& foo(A const& bar)
    {
        return bar;
    }
    //...
    A const& broken = foo(A());
    

    在这两种情况下,临时变量都绑定到函数参数(隐式 this 对应 nothing()bar 对应 foo())并将其生命周期“延长”到函数参数的生命周期。我把'extended'放在引号里,因为临时的自然生命周期已经更长了,所以没有实际的扩展发生。

    因为生命周期延长属性是不可传递的,返回一个引用(恰好引用一个临时对象)不会进一步延长临时对象的生命周期,结果refnopbroken 都结束了up 引用不再存在的对象。

    【讨论】:

    • 虽然 C++ 中的其他类型规则对于防止错误非常严格,但事实上 C++ 标准如此秘密地允许使用临时对象来初始化 const 引用,然后允许临时对象的生命周期如果引用被传递太多次,过期是非常令人沮丧的。当然不能相信保持对 const 作为类成员的引用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-07
    • 2012-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多