“T const&t = C().a;”是否有效?延长“a”的寿命?

发布于 2024-11-01 19:31:00 字数 436 浏览 5 评论 0原文

给出了以下场景,将其解释为 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 获得的行为

谢谢!

The following scenario is given, to be interpreted as C++0x code:

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

Clang and GCC (trunk version as of 2011/02) behave differently: Clang lengthens the lifetime. GCC moves B to a new temporary object, and then binds the reference to that new temporary.

I cannot find either behavior can be derived from the words of the Standard. The expression A().b is not a temporary (see 5.2.5). Can anyone please explain the following to me?

  • Desired behavior (the intent of the committee)
  • The behavior as you derive it from the FDIS

Thanks!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

北风几吹夏 2024-11-08 19:31:00

N3126=10-0116 的 12.2 第 5 段指出:

第二个上下文 [ 其中临时变量是
在与结束不同的时间点被摧毁
完整表达式 ] 是当引用
必然是暂时的。临时的
引用所绑定的或
临时的即完整的对象
引用的子对象的
是绑定持续存在的生命周期
参考除了...

,然后跟随四个特殊情况的列表(构造函数初始化器、引用参数、返回值、新初始化器)。

所以(在这个版本中)在我看来 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++ 标准要求的错误,如果这确实是预期的行为或可能可接受的行为行为(但我必须说,我并没有真正考虑很多,这只是一种感觉,这种差异化有问题)。

In 12.2 paragraph 5 of N3126=10-0116 it's said that:

The second context [ in which temporaries are
destroyed at a different point than the end of
the full-expression ] is when a reference
is bound to a temporary. The temporary
to which the reference is bound or the
temporary that is the complete object
of a subobject to which the reference
is bound persists for the lifetime of the
reference except ...

and then follows a list of four special cases (ctor-inizializers, reference parameters, returned value, new initializer).

So (in this version) seems to me that clang is correct because you're binding the reference to a subobject of a temporary.

EDIT

Thinking to the base sub-object of an object this also seems to be the only reasonable behavior. The alternative would mean doing a slicing in:

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

Actually after making a little experiment seems indeed that g++ differentiates between a member sub-object and a base sub-object, but I don't understand where this differentiation is made in the standard. The following is the test program I used and where it's clearly visible the different handling of the two cases... (B is Base, D is Derived and C is composed).

#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;
}

The output I get with g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 is

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()

In my opinion this is either a bug in g++ or a bug in what the c++ standard mandates if this is really the expected behavior or a possible acceptable behavior (but I must tell that I didn't really think about it a lot, this is just a feeling that something is wrong with this differentiation).

束缚m 2024-11-08 19:31:00

好吧,我对此做了一个 180 度的转变

在刷新了我对标准的了解之后,我不得不承认
预期 b 引用的对象在 const& 的作用域持续时间内保持活动状态(被扩展)可能是正确的。已初始化。我发现 GotW #88< /a> 对此有帮助的来源。

我看不出 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
}

抱歉我可能造成的任何混乱有何不同。我在那里有点力不从心。

Okay, I'm doing a 180 degrees on this

After refreshing my knowledge of the standard, I have to admit
that it is probably right to expect the object referred to by b to remain alive (be extended) for the duration of scope in which the const& was initialized. I found GotW #88 a helpful source for this.

I fail to see how A().b is structurally or semantically different from

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
}

Sorry for any confusion I might have caused. I was a little out of my depth there.

剪不断理还乱 2024-11-08 19:31:00

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

对于由引用声明符创建的临时对象,第 12.2 节让我们参考第 8.5 节。 C++03 和 C++11 在 §8.5.3 中存在很大差异,但两者都明确支持您的代码。

C++03 说

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

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

讨论完全是从子对象的角度进行的,没有区分基类和成员。因此,如果不允许将引用绑定到成员,那么将成员绑定到基类也是如此,这排除了 ScopeGuard。

C++11 更详细,但指定

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

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

Temporary objects are distinguished by the circumstances of their creation. (§12.2 "Temporaries of class type are created in various contexts…")

For temporaries created by a reference declarator, §12.2 refers us to §8.5. C++03 and C++11 differ greatly in §8.5.3, but both clearly support your code.

C++03 says that either

— The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.

— A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. The reference is bound to the temporary or to a sub-object within the temporary.

The discussion is entirely in terms of subobjects, not distinguishing base classes from members. So, if binding a reference to a member is disallowed, then so is binding a member to a base, which rules out ScopeGuard.

C++11 is more verbose, but specifies

— Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference. … If the initializer expression … is an xvalue, class prvalue, array prvalue or function lvalue and “cv1 T1” is reference-
compatible with “cv2 T2” … then the reference is bound to the value of the initializer expression."

Combined with 6502's answer, and the pointlessness of binding a reference to a value which ends at the semicolon, it is apparent that C++11 continues to support this behavior.

疏忽 2024-11-08 19:31:00

让我们看看(所有引用均针对 FDIS):

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

1) 5.2.3/2 表示 A() 是纯右值。

2) 5.2.5/4 表示由于第 1) 点,A().b 是纯右值。

3) 8.5.3/5 表示 B const& b 直接绑定A().b而不创建临时文件。

4) 12.2/5 表示临时绑定到引用的生命周期得到了延长。

所以至少 GCC 在这里是错误的。

Clang 是否正确或者是否是 UB 取决于临时对象的子对象本身是否是临时对象。我很确定答案应该是肯定的,但标准似乎对此事保持沉默。应该有人提交 DR 吗?

编辑:正如@6502所说,3.7.5表示子对象的生命周期是其完整对象的生命周期。

Let's see (all references are to the FDIS):

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

1) 5.2.3/2 says A() is a prvalue.

2) 5.2.5/4 says that A().b is a prvalue because of point 1).

3) 8.5.3/5 says that B const& b binds directly to A().b without creating a temporary.

4) 12.2/5 says that the lifetime of a temporary bound to a reference is extended.

So it appears at least that GCC is wrong here.

Whether Clang is correct or if this is UB depends on whether the subobject of a temporary is itself a temporary. I'm quite sure the answer should be affirmative, but the Standard seems silent about the matter. Should someone submit a DR?

EDIT: As @6502 said, 3.7.5 indicates that the lifetime of a subobject is the lifetime of its complete object.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文