Реализация
Ранее, не подозревая о существовании std::addressof
, почему он существует, имеет смысл для меня: как способ принятия адреса при наличии перегруженного operator&
. Однако реализация несколько непрозрачна. Из gcc 4.7.1
:
template<typename _Tp>
inline _Tp*
__addressof(_Tp& __r) _GLIBCXX_NOEXCEPT
{
return reinterpret_cast<_Tp*>
(&const_cast<char&>(reinterpret_cast<const volatile char&>(__r)));
}
reinterpret_cast<_Tp*>
очевиден. Остальная часть - темная магия. Может кто-то сломать, как это работает?
Ответы
Ответ 1
- Сначала у вас есть
__r
, который имеет тип _Tp&
- Это
reinterpret_cast
'ed для char&
, чтобы обеспечить возможность последующего использования своего адреса, не опасаясь перегруженного operator&
в исходном типе; на самом деле он отличен от const volatile char&
, потому что reinterpret_cast
всегда может законно добавлять квалификаторы const
и volatile
, даже если они отсутствуют, но они не могут удалить их, если они присутствуют (это гарантирует, что любые квалификаторы _Tp
изначально, они не мешают листу).
- Это
const_cast
'ed только для char&
, удаление классификаторов (легально сейчас! const_cast
может делать то, что reinterpret_cast
не может по отношению к квалификаторам).
- Адрес берется
&
(теперь у нас есть простой char*
)
-
reinterpret_cast
'возвращается _Tp*
(который включает в себя исходные const
и volatile
квалификаторы, если они есть).
Изменить:, так как мой ответ был принят, я буду тщательным и добавлю, что выбор char
как промежуточного типа связан с проблемами выравнивания, чтобы избежать запуска Undefined Поведение. См. Комментарии @JamesKanze (по вопросу) для полного объяснения. Спасибо Джеймсу за то, что он объяснил это так ясно.
Ответ 2
На самом деле довольно просто, когда вы думаете об этом, чтобы получить реальный адрес объекта/функции в условиях перегруженного operator&
, вам нужно будет обрабатывать объект как нечто иное, чем то, что оно есть на самом деле, некоторый тип, который не может иметь перегруженный оператор.. собственный тип (например, char
).
A char
не имеет выравнивания и может находиться где угодно, любой другой объект может с этим сказать; бросание объекта на ссылку на char - очень хорошее начало.
Но как насчет черной магии при выполнении reinterpret_cast<const volatile char&>
?
Чтобы переинтерпретировать возвращенный указатель из реализации addressof
, мы в конце концов захотим отбросить квалификаторы, такие как const
и volatile
(чтобы получить равную ссылку char
). Эти два могут быть легко добавлены с помощью reinterpret_cast
, но просить его удалить их является незаконным.
T1 const a; reinterpret_cast<T2&> (a);
/* error: reinterpret_cast from type ‘...’ to type ‘...’ casts away qualifiers */
Это немного "лучше безопасный, чем извините" трюк.. "Давайте добавим их, на всякий случай, мы удалим их позже".
Позже мы отбросили квалификаторы (const и volatile) с помощью const_cast<char&>
, чтобы получить равную ссылку на char
, этот результат в качестве последнего шага превратился в указатель на любой тип, который мы передали нашей реализации.
Соответствующий вопрос на этом этапе заключается в том, почему мы не пропустили использование reinterpret_cast
и перешли непосредственно к const_cast
? у этого тоже есть простой ответ: const_cast
может добавлять/удалять квалификаторы, но он не может изменить базовый тип.
T1 a; const_cast<T2&> (a);
/* error: invalid const_cast from type ‘T1*’ to type ‘T2*’ */
это может быть не так просто, как пирог, но на вкус он хорош, когда вы его получите.
Ответ 3
Краткая версия:
operator&
не может быть перегружен для char
. Таким образом, тип приводится к ссылке на char
чтобы получить то, что гарантированно будет истинным адресом.
Это преобразование выполняется в два приведения из-за ограничений на const_cast
и reinterpret_cast
.
Более длинная версия:
Он выполняет три последовательных приведения.
reinterpret_cast<const volatile char&>
Это фактически приведение к char&
. const
и volatile
существует только потому, что _Tp
может быть const
или volatile
, и reinterpret_cast
можно добавить те, но не смогли бы удалить их.
const_cast<char&>
Теперь const
и volatile
были удалены. const_cast
может сделать это.
reinterpret_cast<_Tp*> &(result)
Теперь адрес берется, и тип преобразуется обратно в указатель на исходный тип.
Ответ 4
С наизнанку:
-
Сначала он присваивает __r
тип const volatile char&
: он отбрасывает char&
только потому, что это тип, который, безусловно, не имеет перегруженного operator&
, который делает что-то фанки. const volatile
существует, потому что это ограничения, их можно добавить, но не удалить с помощью reinterpret_cast
. _Tp
может быть уже const
и/или volatile
, и в этом случае один или оба были необходимы в этом приведении. Если это не так, актеры просто добавили их бесполезно, но написано для наиболее ограничительного актера.
-
Затем, чтобы убрать const volatile
, вам понадобится const_cast
, что приведет к следующей части... const_cast<char&>
.
-
Оттуда они просто берут адрес и отбрасывают его по типу, который вы хотите, _Tp*
. Обратите внимание, что _Tp
может быть const
и/или volatile
, что означает, что эти вещи могут быть добавлены обратно в этот момент.