Ручное построение тривиального базового класса с помощью размещения-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
-
Инициализируйте подходящий объект Base
с помощью размещения-new
-
Построить Foo
с помощью размещения-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};
}
Так как Base
тривиально, я понимаю, что:
-
Пропуск тривиального деструктора Base
, построенного в (2)
, прекрасен;
-
Тривиальный конструктор по умолчанию в подобъекте Base
, построенный как часть Foo
at (3)
, ничего не делает;
И поэтому Foo
получает инициализированный указатель, и все хорошо.
Конечно, это то, что происходит на практике, даже при -O3 (смотрите сами!).
Но безопасно ли это, или дракон схватит и съест меня однажды?
Ответы
Ответ 1
Кажется, что стандарт явно запрещен.
Завершение жизни объекта и запуск новых объектов
время жизни в том же месте явно разрешено,
если это базовый класс:
§3.8 Срок службы объекта
§3.8.7 - Если после окончания срока жизни объекта и до хранения который занятый объект повторно используется или освобождается, новый объект созданный в месте хранения, в котором находился исходный объект, указатель, указывающий на исходный объект, ссылка, к исходному объекту, или имя исходного объекта будет автоматически ссылаются на новый объект и, как только время жизни новый объект, который можно использовать для управления новым объектом, , если:
-
хранилище для нового объекта точно накладывает место хранения который первоначальный объект занял, и
-
новый объект тот же тип, что и исходный объект (игнорируя верхний уровень cv-qualifiers) и
-
[snip] и
-
исходный объект был наиболее производным объектом (1.8) типа T и новый объект является самым производным объектом типа T (т.е. они не являются субобъекты базового класса).