这个C++临时绑定引用成员是否应该是非法的?

mar*_*tin 6 c++ temporary object-lifetime

我的问题(在此之后,对于长篇介绍感到抱歉,问题在于粗体)最初的灵感来自于Herb Sutters Exceptional C++中的第23项,我们发现这样的内容:
<snip>


...
int main()
{
  GenericTableAlgorithm a( "Customer", MyWorker() );
  a.Process();
}


class GenericTableAlgorithm
{
public:
  GenericTableAlgorithm( const string& table,
                         GTAClient&    worker );
  bool Process(); 
private:
  struct GenericTableAlgorithmImpl* pimpl_; //implementation
};
class GTAClient
{
   ///...
   virtual bool ProcessRow( const PrimaryKey& ) =0;
   //...
};
class MyWorker : public GTAClient 
{
  // ... override Filter() and ProcessRow() to
  //     implement a specific operation ...
};


</剪断>

现在,我对该代码存在以下问题(不,我绝不怀疑Sutter先生作为C++专家的实力):

    1. 像这样的例子是行不通的,因为GTAClient&worker是一个非const引用,它不能暂时占用,但好吧,它可能已被写成预标准或错字,无论如何,这不是我的观点.
    2. 令我不知道的是,即使问题1被忽略,他将如何处理工人参考.
      显然意图是MyWorker在(多态)接口GenericTableAlgorithm访问的NVI中使用GTAClient; 这排除了实现拥有类型的(值)成员GTAClient,因为这会导致切片等.值 - 语义不能与多态性很好地混合.
      它不能具有类型的数据成员,MyWorker因为该类是未知的GenericTableAlgorithm.
      因此我得出结论,它必须通过指针或引用来使用,保留原始对象和多态性.
    3. 由于指向临时对象(MyWorker())的指针很少是个好主意,我假设作者的计划是使用绑定到(const)引用的临时生命的延长生命周期,并将这样的引用存储在对象pimpl_中并从那里使用它.(注意:GTAClient中也没有克隆成员函数,这可能使得这个工作;让我们不要假设在后台有潜伏的基于RTTI-typeinfo的工厂.)
      在这里(最后!)我的问题设置如下:(如何)可以合法地将临时的传递给具有延长寿命的类参考成员?


    §12.2.5中的标准(C++ 0x版本,但它在C++中是相同的,不知道章节编号)从生命周期扩展中产生以下异常: " - 临时绑定到构造函数中的引用成员 ctor-initializer(12.6.2)一直存在,直到构造函数退出."

    因此该对象不能用于客户端代码的调用a.Process(); 因为引用的临时来自MyWorker()已经死了!

    现在考虑我自己制作的一个示例来演示问题(在GCC4.2上测试):

    #include <iostream>
    using std::cout; 
    using std::endl;
    
    struct oogie {
     ~oogie()
     {
      cout << "~oogie():" << this << ":" << m_i << endl;
     }
     oogie(int i_)
      : m_i(i_)
     {
      cout << "oogie():" << this << ":" << m_i << endl;
     }
    
     void call() const
     {
      cout << "call(): " << this << ":" << m_i << endl;
     }
     int m_i;
    };
    
    oogie func(int i_=100)
    {
     return oogie(i_);
    }
    
    struct kangoo 
    {
     kangoo(const oogie& o_)
     : m_o(o_)
     {
     }
    
     const oogie& m_o;
    };
    
    int main(int c_, char ** v_)
    {
    
     //works as intended
     const oogie& ref = func(400);
     //kablewy machine
     kangoo s(func(1000));
    
     cout << ref.m_i << endl;
    
     //kangoo's referenced oogie is already gone
     cout << s.m_o.m_i << endl;
    
     //OK, ref still alive
     ref.call();
     //call on invalid object
     s.m_o.call();
    
     return 0;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    产生输出

    oogie():0x7fff5fbff780:400
    oogie():0x7fff5fbff770:1000
    ~oogie():0x7fff5fbff770:1000
    400
    1000
    call(): 0x7fff5fbff780:400
    call(): 0x7fff5fbff770:1000
    ~oogie():0x7fff5fbff780:400
    

    你可以看到,在const oogie&ref的情况下,func()的立即绑定到引用的临时返回值具有所述引用的延长生命周期(直到main的结尾),所以没关系.
    但是:1000-oogie对象在kangoo-s构建之后就已经被破坏了.代码有效,但我们在这里处理一个不死对象......

    所以再次提出问题:
    首先,我在这里遗漏了什么,代码是正确/合法的吗?.
    其次,为什么海湾合作委员会没有给我任何警告,即使用-Wall指定?应该是?可以吗?

    谢谢你的时间,
    马丁

  • Dav*_*eas 4

    我认为这是一个棘手的部分,不太清楚。就在几天前,也有类似的问题。

    默认情况下,当创建临时对象的完整表达式完成时,它们将以与构造相反的顺序销毁。到目前为止,一切都很好并且可以理解,但是随后出现了异常(12.2 [class.temporary]/4,5),事情变得令人困惑。

    我不会处理标准中的确切措辞和定义,而是从工程/编译器的角度来处理问题。当函数完成时,堆栈帧被释放(堆栈指针移回到函数调用开始之前的原始位置),在堆栈中创建临时对象。

    这意味着临时对象永远无法在创建它的函数中存活下来。更准确地说,它无法在定义它的范围内生存,即使它实际上可以在创建它的完整表达式中生存。

    标准中的任何异常都不会超出此限制,在所有情况下,临时变量的生命周期都会延长到保证不超过创建临时变量的函数调用的程度。