通过 placement-new 手动构建一个平凡的基础 class
Manually constructing a trivial base class via placement-new
小心,我们正在绕过巨龙的巢穴。
考虑以下两个 类:
struct Base {
std::string const *str;
};
struct Foo : Base {
Foo() { std::cout << *str << "\n"; }
};
如您所见,我正在访问一个未初始化的指针。还是我?
假设我只使用 Base
类 trivial,只不过是(可能嵌套的)指针包。
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
的微不足道的析构函数是可以的;
在 (3)
作为 Foo
的一部分构造的 Base
子对象的普通默认构造函数不执行任何操作;
因此 Foo
收到一个初始化指针,一切正常。
当然,这就是实践中发生的情况,即使在 -O3 (see for yourself!)。
但是这样安全吗,要不哪天被龙抢食了?
标准似乎明确禁止。
结束一个对象的生命周期,并开始一个新的对象
明确允许在同一位置的生命周期,
除非它是一个基础class:
§3.8 Object Lifetime
§3.8.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
[snip] and
the original object was a most derived object (1.8) of type T and the
new object is a most derived object of type T (that is, they are not
base class subobjects).
小心,我们正在绕过巨龙的巢穴。
考虑以下两个 类:
struct Base {
std::string const *str;
};
struct Foo : Base {
Foo() { std::cout << *str << "\n"; }
};
如您所见,我正在访问一个未初始化的指针。还是我?
假设我只使用 Base
类 trivial,只不过是(可能嵌套的)指针包。
static_assert(std::is_trivial<Base>{}, "!");
我想分三步构建Foo
:
为
Foo
分配原始存储
通过 placement-new
初始化一个适当放置的 通过 placement-new 构造
Foo
。
Base
子对象
我的实现如下:
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
的微不足道的析构函数是可以的;在
(3)
作为Foo
的一部分构造的Base
子对象的普通默认构造函数不执行任何操作;
因此 Foo
收到一个初始化指针,一切正常。
当然,这就是实践中发生的情况,即使在 -O3 (see for yourself!)。
但是这样安全吗,要不哪天被龙抢食了?
标准似乎明确禁止。 结束一个对象的生命周期,并开始一个新的对象 明确允许在同一位置的生命周期, 除非它是一个基础class:
§3.8 Object Lifetime
§3.8.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
[snip] and
the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).