gpt4 book ai didi

c++ - 通过命名成员调用虚拟与地址或引用的区别

转载 作者:塔克拉玛干 更新时间:2023-11-03 02:07:56 24 4
gpt4 key购买 nike

更新如下:在 clang 中,通过其名称使用多态对象的左值不会激活虚拟分派(dispatch),但会通过其地址激活。

对于下面的基类B和派生D,虚函数something, union Space

#include <iostream>
using namespace std;

struct B {
void *address() { return this; }
virtual ~B() { cout << "~B at " << address() << endl; }
virtual void something() { cout << "B::something"; }
};

struct D: B {
~D() { cout << "~D at " << address() << endl; }
void something() override { cout << "D::something"; }
};

union Space {
B b;
Space(): b() {}
~Space() { b.~B(); }
};

如果您的 s 值为 Space,在 Clang++ 中:(更新:错误地声称 g++ 具有相同的行为)如果您执行 s.b.something(),将调用 B::something(),而不是对 s.b 执行动态绑定(bind),但是,如果您调用 (&s.b)->something() 将动态绑定(bind)到 b 真正包含的内容(BD).完成代码是这样的:

union SpaceV2 {
B b;
SpaceV2(): b() {}
~SpaceV2() { (&b)->~B(); }
};

static_assert(sizeof(D) == sizeof(B), "");
static_assert(alignof(D) == alignof(B), "");

#include <new>

int main(int argc, const char *argv[]) {
{
Space s;
cout << "Destroying the old B: ";
s.b.~B();
new(&s.b) D;
cout << "\"D::something\" expected, but \"";
s.b.something();
cout << "\" happened\n";
auto &br = s.b;
cout << "\"D::something\" expected, and \"";
br.something();
cout << "\" happened\n";
cout << "Destruction of D expected:\n";
}
cout << "But did not happen!\n";
SpaceV2 sv2;
new(&sv2.b) D;
cout << "Destruction of D expected again:\n";
return 0;
}

当使用 -O2 优化编译并运行程序时,这是输出:

$./a.out 
Destroying the old B: ~B at 0x7fff4f890628
"D::something" expected, but "B::something" happened
"D::something" expected, and "D::something" happened
Destruction of D expected:
~B at 0x7fff4f890628
But did not happen!
Destruction of D expected again:
~D at 0x7fff4f890608
~B at 0x7fff4f890608

令我惊讶的是,使用 placement new 设置 s.b 的动态类型会导致通过其名称或通过其名称在完全相同的左值上调用 something 的差异地址。第一个问题很重要,但我一直没能找到答案:

  1. 是否根据 C++ 标准对派生类放置新的,如 new(&s.b) D 未定义的行为
  2. 如果不是未定义的行为,那么这种不通过命名成员的左值激活虚拟分派(dispatch)的选择是在标准中指定的东西还是在 G++、Clang 中的选择?

谢谢,我在 S.O. 中的第一个问题曾经。

更新引用标准的答案和评论是准确的:根据标准,s.b 将永远引用确切类型 B 的对象,内存 允许更改类型,但是通过 s.b 对该内存的任何使用都是“未定义行为”,即被禁止,或者编译器可以随心所欲地进行翻译。如果 Space 只是一个字符缓冲区,就地构造、析构和更改类型都是有效的。在导致这个问题的代码中正是这样做的,它适用于符合标准的 AFAIK。谢谢。

最佳答案

表达式 new(&s.b) D; 重新使用名为 s.b 且之前被 B 占用的存储用于存储一个新的 D

但是您随后编写了 s.b.something(); 。这会导致未定义的行为,因为 s.b 表示 B,但存储在该位置的实际对象是 D。参见 C++14 [basic.life]/7:

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and

  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and

    [...]

不满足最后一个要点,因为新类型不同。

(代码后面还有其他潜在问题,但由于此处导致了未定义的行为,因此它们没有实际意义;您需要进行重大设计更改才能避免此问题)。

关于c++ - 通过命名成员调用虚拟与地址或引用的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41456172/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com