6

我的问题(这将在此之后出现,对于冗长的介绍感到抱歉,问题在下面以粗体显示)最初是受到 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 ...
};


</snip>

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

    1. 像这样的例子是行不通的,因为 GTAClient& worker 是一个非常量引用,不能临时引用,但是好吧,它可能是按标准编写的或者是错字,无论如何,这不是我的意思。
    2. 让我想知道的是他将如何处理工人参考,即使问题 1. 被忽略。
      显然本意是用在通过(多态)接口访问MyWorker的NVI中;这排除了实现拥有 type 的(值)成员,因为这会导致切片等。值语义不能与多态性很好地混合。 它也不能具有类型的数据成员,因为该类对于. 所以我得出结论,它一定是通过指针或引用来使用的,保留了原始对象和多态性。GenericTableAlgorithmGTAClientGTAClient
      MyWorkerGenericTableAlgorithm
    3. 由于指向临时对象MyWorker()pimpl_. (注意:GTAClient 中也没有 clone-member 函数,它可以完成这项工作;我们不要假设有一个基于 RTTI-typeinfo 的工厂潜伏在后台。
      在这里(终于!)我的问题是: (如何)可以合法地将临时 to 传递给具有延长生命周期的类的引用成员?


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

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

    现在考虑一个我自己制作的示例来演示该问题(在 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;
    }
    

    产生输出

    oogie():0x7fff5fbff780:400
    oogie():0x7fff5fbff770:1000
    ~oogie():0x7fff5fbff770:1000
    400
    1000
    调用():0x7fff5fbff780:400
    调用():0x7fff5fbff770:1000
    ~oogie():0x7fff5fbff780:400
    

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

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

    谢谢你的时间,
    马丁

  • 4

    4 回答 4

    4

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

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

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

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

    标准中的任何例外都不受此限制,在所有情况下,临时文件的生命周期都被延长到保证不超过创建临时文件的函数调用的点。

    于 2009-09-25T22:25:59.730 回答
    3

    本周大师文章的原版在这里:本周大师#15

    您的部分答案可能来自 Herb 本人,在“最重要的 const 的候选人”中 - 将临时分配给参考的“const”部分确实很重要。

    因此,这似乎是原始文章中的一个错误。

    于 2009-09-25T12:46:02.813 回答
    1

    我一直认为传递地址参数(无论是通过引用还是指针)需要了解所传递事物的生命周期。时期。讨论完毕。这似乎只是 un-garbage-collected/NOT-reference-managed 环境的一个属性。

    这是 GC 的好处之一。

    在 c++ 中,有时生命周期问题通过以下方式解决:

    X::克隆

    或通过接口的显式文档,例如:

    “由 Y 的实例化器来确保在参数 x 中传递的 X 的实例在 Y 的整个生命周期内保持存在”

    或通过

    x 在接收器内部的显式复制。

    那只是c++。C++ 在 const 引用上添加了一个保证很好(而且确实有点必要),但就是这样。

    因此,我认为问题中显示的代码是错误的,并且我认为它应该是:

    int main()
    {
      MyWorker w;
      GenericTableAlgorithm a( "Customer", w);
      a.Process();
    }
    
    于 2009-09-25T15:28:34.800 回答
    0

    我认为你不能用当前的 C++ 做到这一点。您需要将在 C++x0 中引入的移动语义。

    于 2009-09-25T12:45:48.787 回答