Выровненное хранилище и строгое наложение
В настоящее время я использую align_storage для реализации типа 'Optional', аналогичного типу boost::option. Для этого у меня есть такой ученик:
typename std::aligned_storage<sizeof(T), std::alignment_of<T>::value>::type t_;
Я использую размещение new, чтобы создать объект, однако я не храню указатель, возвращенный где-либо. Вместо этого я обращаюсь к базовому типу объекта во всех моих функциях-членах, как это (очевидно, с помощью проверок, чтобы убедиться, что объект действителен через логический флаг, также хранящийся в моем дополнительном типе):
T const* operator->() const {
return static_cast<T const*>(static_cast<void const*>(&t_));
}
Мой вопрос: безопасно ли это? Насколько я понимаю, мое использование размещения new меняет "динамический тип" объекта, и до тех пор, пока я продолжаю получать доступ к памяти, используя этот тип, я буду в порядке. Однако мне не ясно, нужно ли мне удерживать указатель, возвращаемый из места размещения, новым или мне разрешено просто приводить к базовому типу всякий раз, когда мне нужно получить к нему доступ. Я прочитал раздел 3.10 стандарта C++11, однако я не достаточно свободно владею стандартами, чтобы быть уверенным.
Если возможно, я бы чувствовал себя лучше, если бы вы могли дать ссылку на стандарт в своем ответе (это помогает мне спать по ночам:P).
1 ответ
ABICT ваше использование безопасно.
- Размещение нового объекта типа T создаст объект, начиная с переданного адреса.
§5.3.4 / 10 говорит:
Выражение new передает количество пространства, запрошенное для функции выделения, в качестве первого аргумента типа std::size_t. Этот аргумент должен быть не меньше размера создаваемого объекта; он может быть больше, чем размер создаваемого объекта, только если объект является массивом.
Для объекта, не являющегося массивом, выделенный размер не может быть больше размера объекта, поэтому представление объекта должно начинаться с начала выделенной памяти, чтобы соответствовать размеру.
Placement new возвращает переданный указатель (см. § 18.6.1.3/2) как результат "размещения", поэтому представление объекта построенного объекта начнется с этого адреса.
static_cast<>
и неявные преобразования междуT*
тип иvoid*
преобразовать между указателем на объект и указателем на его хранилище, если объект является законченным объектом.
§4.10 / 2 говорит:
Значение типа "указатель на cv T", где T - тип объекта, может быть преобразовано в значение типа "указатель на 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" может быть преобразовано в значение типа "указатель на cv2 T", где T - это тип объекта, а cv2 - та же квалификация cv, что и cv1 или более высокая квалификация, чем 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