20

次のシナリオは、C++0x コードとして解釈されます。

struct B { }; 
struct A { B b; }; 
int main() { 
  B const& b = A().b; 
  /* is the object still alive here? */
}

Clang と GCC (2011/02 時点のトランク バージョン) の動作は異なります。Clang は寿命を延ばします。GCC は新しい一時オブジェクトに移動Bし、参照をその新しい一時オブジェクトにバインドします。

どちらの動作も、標準の言葉から導き出すことができるとは思いません。式A().bは一時的なものではありません (5.2.5 を参照)。誰か私に次のことを説明してもらえますか?

  • 望ましい行動(委員会の意図)
  • FDIS から導き出した動作

ありがとう!

4

4 に答える 4

11

N3126=10-0116 の 12.2 パラグラフ 5 では、次のように述べられています。

2 番目のコンテキスト [完全な式の末尾とは異なる時点で一時変数が破棄される] は、参照が一時変数にバインドされている場合です。参照がバインドされている一時オブジェクト、または参照がバインドされているサブオブジェクトの完全なオブジェクトである一時オブジェクトは、... を除き、参照の存続期間中存続します。

その後、4 つの特殊なケース (ctor-initializers、参照パラメーター、戻り値、新しい初期化子) のリストに従います。

したがって、(このバージョンでは) 参照を一時オブジェクトのサブオブジェクトにバインドしているため、clang は正しいように思えます。

編集

オブジェクトの基本サブオブジェクトを考えると、これも唯一の合理的な動作のようです。別の方法は、次のようにスライスすることを意味します。

Derived foo();
...
void bar()
{
    Base& x = foo(); // not very different from foo().b;
    ...
}

実際に少し実験を行った後、g ++ はメンバー サブオブジェクトとベース サブオブジェクトを区別しているように見えますが、この区別が標準のどこで行われているかはわかりません。以下は、私が使用したテスト プログラムであり、2 つのケースの処理の違いが明確にわかります... (Bはベース、D派生、C構成)。

#include <iostream>

struct B
{
    B()
    { std::cout << "B{" << this << "}::B()\n"; }

    B(const B& x)
    { std::cout << "B{" << this << "}::B(const B& " << &x << ")\n"; }

    virtual ~B()
    { std::cout << "B{" << this << "}::~B()\n"; }

    virtual void doit() const
    { std::cout << "B{" << this << "}::doit()\n"; }
};

struct D : B
{
    D()
    { std::cout << "D{" << this << "}::D()\n"; }

    D(const D& x)
    { std::cout << "D{" << this << "}::D(const D& " << &x << ")\n"; }

    virtual ~D()
    { std::cout << "D{" << this << "}::~D()\n"; }

    virtual void doit() const
    { std::cout << "D{" << this << "}::doit()\n"; }
};

struct C
{
    B b;

    C()
    { std::cout << "C{" << this << "}::C()\n"; }

    C(const C& x)
    { std::cout << "C{" << this << "}::C(const C& " << &x << ")\n"; }

    ~C()
    { std::cout << "C{" << this << "}::~C()\n"; }
};

D foo()
{
    return D();
}

void bar()
{
    std::cout << "Before calling foo()\n";
    const B& b = foo();
    std::cout << "After calling foo()\n";
    b.doit();
    std::cout << "After calling b.doit()\n";

    const B& b2 = C().b;
    std::cout << "After binding to .b\n";
    b2.doit();
    std::cout << "After calling b2.doit()\n";
}

int main()
{
    std::cout << "Before calling bar()\n";
    bar();
    std::cout << "After calling bar()\n";
    return 0;
}

g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 で得られる出力は

Before calling bar()
Before calling foo()
B{0xbf9f86ec}::B()
D{0xbf9f86ec}::D()
After calling foo()
D{0xbf9f86ec}::doit()
After calling b.doit()
B{0xbf9f86e8}::B()
C{0xbf9f86e8}::C()
B{0xbf9f86e4}::B(const B& 0xbf9f86e8)
C{0xbf9f86e8}::~C()
B{0xbf9f86e8}::~B()
After binding to .b
B{0xbf9f86e4}::doit()
After calling b2.doit()
B{0xbf9f86e4}::~B()
D{0xbf9f86ec}::~D()
B{0xbf9f86ec}::~B()
After calling bar()

私の意見では、これは g++ のバグか、これが実際に期待される動作または許容可能な動作である場合、c++ 標準で義務付けられているバグのいずれかです (しかし、私はそれについてあまり考えていなかったと言わなければなりません。これはこの差別化に何か問題があると感じただけです)。

于 2011-04-16T21:02:46.237 に答える
1

さて、私はこれで180度やっています

標準に関する私の知識を再確認した後、 const& が初期化されたスコープの期間中、によって参照されるオブジェクトbが存続する (拡張される) ことを期待するのはおそらく正しいことを認めなければなりません。これについては、 GotW #88が役立つ情報源であることがわかりました。

A().bが構造的または意味的にどのように異なるのかわかりません

string f() { return "abc"; } // ABC initializes return-value **TEMP**

void g() {
const string& s = f();  // initializes with reference to a temp
  cout << s << endl;    // '*&s' is extended per standard
}

私が引き起こした混乱をお詫び申し上げます。私はそこで私の深みから少し外れていました。

于 2011-04-16T21:41:09.380 に答える
0

一時オブジェクトは、作成の状況によって区別されます。(§12.2「クラスタイプの一時はさまざまなコンテキストで作成されます…」)

参照宣言者によって作成された一時的なものについては、§12.2は§8.5を参照します。C++03とC++11は§8.5.3で大きく異なりますが、どちらもコードを明確にサポートしています。

C ++ 03は、

—参照は、右辺値(3.10を参照)で表されるオブジェクト、またはそのオブジェクト内のサブオブジェクトにバインドされます。

—タイプ「cv1T2」[原文のまま]の一時オブジェクトが作成され、コンストラクターが呼び出されて、右辺値オブジェクト全体が一時オブジェクトにコピーされます。参照は、一時または一時内のサブオブジェクトにバインドされます。

議論は完全にサブオブジェクトの観点から行われ、基本クラスとメンバーを区別しません。したがって、メンバーへの参照のバインドが許可されていない場合は、メンバーをベースにバインドすることも許可されており、ScopeGuardが除外されます。

C ++ 11はより冗長ですが、

—それ以外の場合、参照は不揮発性constタイプへの左辺値参照(つまり、cv1はconst)であるか、参照は右辺値参照である必要があります。…初期化式…がxvalue、クラスprvalue、配列prvalue、または関数lvalueであり、「cv1T1」が「cv2T2」と参照互換である場合、参照は初期化式の値にバインドされます。」

6502の答え、およびセミコロンで終わる値への参照のバインドの無意味さと組み合わせると、C++11がこの動作を引き続きサポートしていることは明らかです。

于 2011-04-16T21:18:55.473 に答える
0

見てみましょう(すべての参照はFDISへのものです):

struct B { }; 
struct A { B b; }; 
int main() { 
  B const& b = A().b; 
}

1) 5.2.3/2A()は、prvalue であると述べています。

2) 5.2.5/4 はA().b、ポイント 1) のためにそれが prvalue であると言います。

3) 8.5.3/5 は、一時ファイルを作成せずにB const& b 直接バインドすると述べています。A().b

4) 12.2/5 は、参照への一時的なバインドの有効期間が延長されると述べています。

したがって、少なくともここでは GCC が間違っているようです。

Clang が正しいか、これが UB であるかは、一時オブジェクトのサブオブジェクト自体が一時オブジェクトであるかどうかによって異なります。答えは肯定的であるべきだと確信していますが、標準はこの問題について沈黙しているようです. だれかが DR を提出する必要がありますか?

編集: @ 6502 が言ったように、3.7.5 は、サブオブジェクトの有効期間がその完全なオブジェクトの有効期間であることを示します。

于 2011-04-16T22:21:24.993 に答える