Beware, we're skirting the dragon's lair.
Consider the following two classes:
struct Base {
std::string const *str;
};
struct Foo : Base {
Foo() { std::cout << *str << "\n"; }
};
As you can see, I'm accessing an uninitialized pointer. Or am I?
Let's assume I'm only working with Base
classes that are trivial, nothing more than (potentially nested) bags of pointers.
static_assert(std::is_trivial<Base>{}, "!");
I would like to construct Foo
in three steps:
Allocate raw storage for a
Foo
Initialize a suitably-placed
Base
subobject via placement-newConstruct
Foo
via placement-new.
My implementation is as follows:
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};
}
Since Base
is trivial, my understanding is that:
Skipping the trivial destructor of the
Base
constructed at(2)
is fine;The trivial default constructor of the
Base
subobject constructed as part of theFoo
at(3)
does nothing;
And so Foo
receives an initialized pointer, and all is well.
Of course, this is what happens in practice, even at -O3 (see for yourself!).
But is it safe, or will the dragon snatch and eat me one day?