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>

さて、私はそのコードに関して次の問題を抱えています(そして、いいえ、C ++の専門家としてのSutter氏の腕前に疑いの余地はありません):

    1. GTAClient&workerは一時的なものをとることができない非定数参照であるため、このような例は機能しませんが、標準以前に記述されているか、タイプミスである可能性があります。それは私のポイントではありません。
    2. 問題1が無視されたとしても、彼がワーカー参照をどうするのか不思議に思います。
      明らかに、その意図は、 (ポリモーフィック)インターフェースによってアクセスされるMyWorkerNVIで使用することです。これにより、実装がタイプの(値)メンバーを所有することが除外されます。これは、スライスなどが発生するためです。値セマンティクスはポリモーフィズムとうまく混ざりません。 そのクラスはに不明であるため、タイプのデータメンバーを持つこともできません。 したがって、元のオブジェクトとポリモーフィックな性質を維持しながら、ポインターまたは参照を介して使用することを意図していたに違いないと結論付けます。GenericTableAlgorithmGTAClientGTAClient
      MyWorkerGenericTableAlgorithm
    3. 一時オブジェクト()へのポインタがMyWorker()良いアイデアになることはめったにないので、作者の計画は(const)参照にバインドされた一時オブジェクトの延長された寿命を使用し、そのような参照をオブジェクトpimpl_ポイントに格納してそこから使用することだったと思います。(注:GTAClientには、これを機能させる可能性のあるクローンメンバー関数もありません。バックグラウンドにRTTI-typeinfoベースのファクトリが潜んでいるとは限りません。
      そして、ここで(ついに!)私の質問は次のようになります。(どのように)寿命が延長されたクラスの参照メンバーに一時を渡すことは合法的に行うことができますか?


    §12.2.5の標準(C ++ 0xバージョンですが、C ++でも同じです。チャプター番号はわかりません)では、ライフタイム拡張から次の例外が発生します。 "-コンストラクターの参照メンバーに一時的にバインドされます。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
    call():0x7fff5fbff780:400
    call():0x7fff5fbff770:1000
    〜oogie():0x7fff5fbff780:400
    

    const oogie&refの場合、func()のすぐに参照にバインドされた一時的な戻り値は、その参照の存続期間が延長されている(mainの終わりまで)ので、問題ないこと がわかります。
    しかし:1000-oogieオブジェクトは、kangoo-sが構築された直後にすでに破壊されています。コードは機能しますが、ここではアンデッドオブジェクトを扱っています...

    もう一度質問をします。
    まず、ここで何かが足りないので、コードは正しい/合法ですか?
    次に、-Wallが指定されていても、GCCが警告を表示しないのはなぜですか?それが必要ですか?できますか?

    お時間をいただきありがとうございます、
    マーティン

  • 4

    4 に答える 4

    4

    これはあまり明確ではないトリッキーな部分だと思います。ほんの数日前に同様の質問がありました。

    デフォルトでは、一時的なものは、それらが作成された完全な式が完了すると、構築の逆の順序で破棄されます。ここまではすべて問題なく理解されていますが、例外が発生し(12.2 [class.temporary] / 4,5)、混乱を招きます。

    標準の正確な表現と定義を扱う代わりに、エンジニアリング/コンパイラの観点から問題に取り組みます。関数が完了すると、スタックに一時的なものが作成され、スタックフレームが解放されます(関数呼び出しが開始される前に、スタックポインタが元の位置に戻されます)。

    これは、一時的なものが作成された関数を存続させることができないことを意味します。より正確には、それが作成された完全な式を実際に生き残ることができたとしても、それが定義されたスコープを生き残ることはできません。

    標準の例外はいずれもこの制限に該当しません。すべての場合において、一時の有効期間は、一時が作成された関数呼び出しを超えないことが保証されるポイントまで延長されます。

    于 2009-09-25T22:25:59.730 に答える
    3

    元のGuruofthe Weekの記事はここにあります:Guru of the Week#15

    あなたの答えの一部は、「最も重要なconstの候補」で、ハーブ自身から来るかもしれません-参照に一時を割り当てることの「const」部分は確かに重要です。

    したがって、これは元の記事のバグであるかのように見えます。

    于 2009-09-25T12:46:02.813 に答える
    1

    私は常に、アドレスパラメータを(参照またはポインタのどちらで)渡すには、渡されるものの存続期間について理解する必要があると考えていました。限目。議論の終わり。これは、ガベージコレクションされていない/参照管理されていない環境の単なる属性のようです。

    これはGCの利点の1つです。

    C ++では、ライフタイムの問題は次の方法で解決されることがありました。

    X::クローン

    または、インターフェースの明示的な文書化によって、例:

    「パラメータxで渡されたXのインスタンスがYの存続期間全体にわたって存在し続けることを保証するのは、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 に答える