【问题标题】:Manually constructing a trivial base class via placement-new通过placement-new手动构造一个平凡的基类
【发布时间】:2023-03-14 06:17:01
【问题描述】:

小心,我们正在绕过巨龙的巢穴。

考虑以下两个类:

struct Base {
    std::string const *str;
};

struct Foo : Base {
    Foo() { std::cout << *str << "\n"; }
};

如您所见,我正在访问一个未初始化的指针。还是我?

假设我只使用 trivialBase 类,只不过是(可能嵌套的)指针包。

static_assert(std::is_trivial<Base>{}, "!");

我想分三步构造Foo

  1. Foo分配原始存储

  2. 通过placement-new初始化一个适当放置的Base子对象

  3. 通过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


【解决方案1】:

这似乎被标准明确禁止。 结束一个对象的生命周期,并开始一个新的对象 明确允许在同一位置的生命周期, 除非它是一个基类:

§3.8 对象生命周期

§3.8.7 - 如果在对象的生命周期结束之后并且在存储之前 被占用的对象被重用或释放,一个新的对象是 在原始对象占用的存储位置创建,a 指向原始对象的指针,引用的引用 到原始对象,或者原始对象的名称将 自动引用新对象,并且一旦生命周期 新对象已启动,可用于操作新对象,如果

  • 新对象的存储正好覆盖存储位置 原始对象占据了哪个位置,并且

  • 新对象属于 与原始对象相同的类型(忽略顶层 cv 限定符)和

  • [snip] 和

  • 原始对象是 T 类型的最衍生对象 (1.8),并且 新对象是 T 类型的派生最多的对象(也就是说,它们不是 基类子对象)。

【讨论】:

  • 射击。这太残忍了:(
  • 本段只处理原始对象的引用/指针/名称可以重用于引用新对象的情况。我在 OP 的代码中看不到任何这样做的地方。
  • 在最新的草案中,我称之为“不死条款”的条款被修改:条款中没有明确提及“最衍生对象”。但是还有另一个新要求,它排除了基础子对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-21
  • 1970-01-01
  • 2010-09-26
  • 2015-10-17
  • 1970-01-01
  • 2021-11-15
  • 1970-01-01
相关资源
最近更新 更多