通过placement-new手动构建一个简单的基类

Que*_*tin 13 c++ placement-new object-lifetime language-lawyer

当心,我们正在绕过龙的巢穴.

考虑以下两个类:

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

struct Foo : Base {
    Foo() { std::cout << *str << "\n"; }
};
Run Code Online (Sandbox Code Playgroud)

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

让我们假设我只使用Base那些微不足道的类,只不过是(可能是嵌套的)指针.

static_assert(std::is_trivial<Base>{}, "!");
Run Code Online (Sandbox Code Playgroud)

我想Foo分三步构建:

  1. 为a分配原始存储空间 Foo

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

  3. Foo通过placement-new 构建.

我的实现如下:

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};
}
Run Code Online (Sandbox Code Playgroud)

既然Base是微不足道的,我的理解是:

  • 跳过Base构造的琐碎的析构函数(2)很好;

  • Base作为Fooat的一部分构造的子对象的普通默认构造函数(3)不执行任何操作;

因此Foo接收一个初始化指针,一切都很好.

当然,这就是在实践中发生的事情,即使在-O3(请亲自看看!).
但它是安全的,还是龙有一天会抓住并吃掉我?

sp2*_*nny 8

标准似乎明确禁止这一点.除非它是基类,否则显式允许结束对象生存期并在同一位置启动新对象生存期 :

§3.8对象生命周期

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

  • 新对象的存储完全覆盖原始对象占用的存储位置,以及

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

  • [snip]和

  • 原始对象是类型为T的派生程度最高的对象(1.8),新对象是类型为T的派生程度最高的对象(也就是说,它们不是基类子对象).

  • 本段仅涉及何时可以重用原始对象的引用/指针/名称来引用新对象。我在 OP 的代码中没有看到任何这样做的地方。 (2认同)