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

Реализация

Ранее, не подозревая о существовании 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*> очевиден. Остальная часть - темная магия. Может кто-то сломать, как это работает?

4b9b3361

Ответ 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, что означает, что эти вещи могут быть добавлены обратно в этот момент.