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 段中说:

第二个上下文[其中临时对象在与完整表达式末尾不同的点被销毁]是引用绑定到临时对象时。引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象在引用的生命周期内持续存在,除了...

然后是四个特殊情况的列表(ctor-inizializers、引用参数、返回值、新的初始化程序)。

所以(在这个版本中)在我看来,clang 是正确的,因为您将引用绑定到临时的子对象。

编辑

考虑到对象的基本子对象,这似乎也是唯一合理的行为。另一种方法是进行切片:

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

实际上,在做了一个小实验之后,g++ 似乎确实区分了成员子对象和基础子对象,但我不明白这种区分在标准中的位置。以下是我使用的测试程序,可以清楚地看到两种情况的不同处理......(B是 Base,D是 Derived 并且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 度

在刷新了我对标准的知识之后,我不得不承认,期望 所引用的对象b在 const& 初始化的范围内保持活动(被扩展)可能是正确的。我发现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

让我们看看(所有引用都指向 FDIS):

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

1) 5.2.3/2 说A()是prvalue。

2) 5.2.5/4 说这A().b是一个纯右值,因为第 1) 点。

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 回答
0

临时对象通过它们的创建环境来区分。(第 12.2 节“类类型的临时对象是在各种上下文中创建的……”)

对于由引用声明器创建的临时对象,§12.2 将我们引向 §8.5。C++03 和 C++11 在 §8.5.3 中有很大不同,但两者都清楚地支持您的代码。

C++03 说要么

— 引用绑定到由右值表示的对象(参见 3.10)或该对象内的子对象。

— 创建了一个“cv1 T2”[sic] 类型的临时对象,并调用构造函数将整个右值对象复制到临时对象中。引用绑定到临时对象或临时对象中的子对象。

讨论完全是根据子对象,而不是区分基类和成员。因此,如果不允许绑定对成员的引用,那么将成员绑定到基类也是如此,这就排除了 ScopeGuard。

C++11 更冗长,但指定

— 否则,引用应为对非易失性 const 类型的左值引用(即,cv1 应为 const),或者引用应为右值引用。... 如果初始化表达式 ... 是一个 xvalue、类纯右值、数组纯右值或函数左值,并且“cv1 T1”与“cv2 T2”引用兼容……那么引用将绑定到初始化表达式的值。”

结合 6502 的回答,以及将引用绑定到以分号结尾的值的毫无意义,显然 C++11 继续支持这种行为。

于 2011-04-16T21:18:55.473 回答