Подтвердить что ты не робот

Aligned_storage и строгий псевдоним

В настоящее время я использую aligned_storage для реализации типа "Дополнительный", аналогичного типу boost:: optional. Для этого у меня есть член класса, например:

typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;

Я использую новое место для создания объекта, но я не сохраняю указатель, возвращенный где угодно. Вместо этого я обращаюсь к базовому типу объекта во всех моих функциях-членах, подобных этому (очевидно, с проверками, чтобы гарантировать, что объект действителен через логический флаг, также сохраненный в моем необязательном типе):

T const* operator->() const {
    return static_cast<T const*>(static_cast<void const*>(&t_));
}

Мой вопрос: безопасно ли это. Я понимаю, что мое использование места размещения изменяет "динамический тип" объекта, и пока я продолжаю обращаться к памяти с использованием этого типа, я буду в порядке. Однако я не знаю, должен ли я удерживать указатель, возвращенный из нового места размещения, или мне разрешено просто указывать базовый тип всякий раз, когда мне нужно получить к нему доступ. Я прочитал раздел 3.10 стандарта С++ 11, однако я недостаточно уверен в стандартах, чтобы быть уверенным.

Если возможно, мне было бы лучше, если бы вы могли дать ссылку на стандарт в своем ответе (это помогает мне спать по ночам: P).

4b9b3361

Ответ 1

ПОЖАЛУЙСТА, ваше использование безопасно.

  • Размещение нового объекта типа T создаст объект, начинающийся с переданного адреса.

В §5.3.4/10 говорится:

Новое выражение передает объем пространства, запрошенный для функция распределения как первый аргумент типа std:: size_t. Что аргумент должен быть не меньше размера создаваемого объекта; он может быть больше размера создаваемого объекта, только если объект представляет собой массив.

Для объекта без массива назначенный размер не может быть больше размера объекта, поэтому представление объекта должно начинаться в начале выделенной памяти, чтобы оно соответствовало.

Размещение нового возвращает переданный указатель (см. § 18.6.1.3/2) в результате "выделения", поэтому представление объекта построенного объекта начнется с этого адреса.

  • static_cast<> и неявные преобразования между T* type и void* конвертировать между указателем на объект и указателем на его хранилище, если объект является полным объектом.

В §4.10/2 говорится:

Значение типа "указатель на cv T", где T - тип объекта, может быть преобразован в prvalue типа "указатель на cv void" . Результат преобразование "указателя на cv T" в "указатель на cv void" указывает на начало места хранения, где находится объект типа T, так как если объект является наиболее производным объектом (1.8) типа T [...]

Это определяет неявное преобразование для преобразования, как указано. Далее §5.2.9 [expr.static.cast]/4 определяет static_cast<> для явных преобразований, где существует неявное преобразование, чтобы иметь тот же эффект, что и неявное преобразование:

В противном случае выражение e может быть явно преобразовано в тип Tиспользуя static_cast формы static_cast<T>(e), если декларация T t(e); является корректным, для некоторой изобретенной временной переменной T (8.5). Эффект такого явного преобразования такой же, как и выполнение декларации и инициализации, а затем используя временную переменная в результате преобразования. [...]

Для обратного static_cast<> (от void* до T*) в §5.2.9/13 говорится:

Значение типа "указатель на cv1 void" может быть преобразовано в prvalue типа "указатель на cv2 T", где T - тип объекта, а cv2 - такая же CV-квалификация, как или более высокая cv-квалификация, чем cv1. [...] Значение указателя типа на объект, преобразованный в "указатель на cv void" и обратно, возможно с другой cv-квалификацией, должны иметь оригинальное значение.

Итак, если у вас есть void*, указывающий на хранилище объекта T (который является значением указателя, которое будет результатом неявного преобразования T* в объект, тогда static_cast этого до T* будет выдавать действительный указатель на объект.

Возвращаясь к вашему вопросу, предыдущие пункты подразумевают, что если у вас

typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;
void * pvt_ = &t_;

T* pT = new (&t_) T(args...);
void * pvT = pT;

то

  • хранилище *pT точно накладывает байты первого размера (T) хранилища t_, так что pvT == pvt_
  • pvt_ == static_cast<void*>(&t_)
  • static_cast<T*>(pvT) == pT
  • Взято вместе, что дает static_cast<T*>(static_cast<void*>(&t_)) == pT