【发布时间】:2023-03-14 06:17:01
【问题描述】:
小心,我们正在绕过巨龙的巢穴。
考虑以下两个类:
struct Base {
std::string const *str;
};
struct Foo : Base {
Foo() { std::cout << *str << "\n"; }
};
如您所见,我正在访问一个未初始化的指针。还是我?
假设我只使用 trivial 的 Base 类,只不过是(可能嵌套的)指针包。
static_assert(std::is_trivial<Base>{}, "!");
我想分三步构造Foo:
为
Foo分配原始存储通过placement-new初始化一个适当放置的
Base子对象通过placement-new构造
Foo。
我的实现如下:
std::unique_ptr<Foo> makeFooWithBase(std::string const &str) {
static_assert(std::is_trivial<Base>{}, "!");
// (1)
auto storage = std::make_unique<
std::aligned_storage_t<sizeof(Foo), alignof(Foo)>
>();
Foo * const object = reinterpret_cast<Foo *>(storage.get());
Base * const base = object;
// (2)
new (base) Base{&str};
// (3)
new (object) Foo();
storage.release();
return std::unique_ptr<Foo>{object};
}
由于Base是微不足道的,我的理解是:
跳过在
(2)构造的Base的微不足道的析构函数很好;作为
Foo(3)的一部分构造的Base子对象的普通默认构造函数什么都不做;
所以Foo 收到了一个初始化的指针,一切都很好。
当然,这就是实际发生的情况,即使在 -O3 (see for yourself!) 时也是如此。
但是这样安全吗,还是有一天被龙抢过来吃掉我?
【问题讨论】:
-
不保证
(void*)(Base*)pDerived == (void*)pDerived. -
@n.m.我不做这样的假设。这正是
Base * const base = object;的用途,通过隐式转换进行调整。 -
哦,我明白了。我的坏,下次一定看起来更好。但是,如果指针实际上不指向 Foo 对象,那么谁说可以将 Foo* 转换为 Base* 呢?
-
@n.m.不用担心。这实际上是另一个问题——你认为我应该强调一下吗?
-
这保证会破坏虚拟继承,而且我不记得标准为非虚拟继承做出任何额外保证,所以是的,有一个担心的基础。
标签: c++ language-lawyer object-lifetime placement-new