Как я могу надежно получить адрес объекта, когда оператор & перегружен? - PullRequest
167 голосов
/ 27 июня 2011

Рассмотрим следующую программу:

struct ghost
{
    // ghosts like to pretend that they don't exist
    ghost* operator&() const volatile { return 0; }
};

int main()
{
    ghost clyde;
    ghost* clydes_address = &clyde; // darn; that's not clyde's address :'( 
}

Как получить адрес clyde?

Я ищу решение, которое будет одинаково хорошо работать для всех типовобъекты.Решение на C ++ 03 было бы неплохо, но я также заинтересован в решениях на C ++ 11.Если возможно, давайте избегать поведения, специфичного для реализации.

Мне известен шаблон функции std::addressof в C ++ 11, но я не заинтересован в его использовании здесь: я хотел бы понять, как работает стандартная библиотекаРазработчик может реализовать этот шаблон функции.

Ответы [ 5 ]

98 голосов
/ 27 июня 2011

Обновление: в C ++ 11, можно использовать std::addressof вместо boost::addressof.


Давайте сначала скопируем код из Boost, за исключением компилятораОбойти биты:

template<class T>
struct addr_impl_ref
{
  T & v_;

  inline addr_impl_ref( T & v ): v_( v ) {}
  inline operator T& () const { return v_; }

private:
  addr_impl_ref & operator=(const addr_impl_ref &);
};

template<class T>
struct addressof_impl
{
  static inline T * f( T & v, long ) {
    return reinterpret_cast<T*>(
        &const_cast<char&>(reinterpret_cast<const volatile char &>(v)));
  }

  static inline T * f( T * v, int ) { return v; }
};

template<class T>
T * addressof( T & v ) {
  return addressof_impl<T>::f( addr_impl_ref<T>( v ), 0 );
}

Что произойдет, если мы передадим ссылку на функцию ?

Примечание: addressof не можетиспользоваться с указателем на функцию

В C ++, если объявлено void func();, тогда func является ссылкой на функцию, не имеющую аргументов и не возвращающую результата.Эта ссылка на функцию может быть легко преобразована в указатель на функцию - из @Konstantin: согласно 13.3.3.2 и T &, и T * неразличимы для функций.Первый - преобразование идентификаторов, а второй - преобразование функций в указатели, оба из которых имеют ранг «Точное совпадение» (13.3.3.1.1, таблица 9).

Ссылка на функцию пройти через addr_impl_ref, существует неоднозначность в разрешении перегрузки для выбора f, которая решается благодаря фиктивному аргументу 0, который является int первым и может быть повышен до long (Интегральное преобразование).

Таким образом, мы просто возвращаем указатель.

Что произойдет, если мы передадим тип с оператором преобразования?

Еслиоператор преобразования дает T*, тогда мы имеем неоднозначность: для f(T&,long) для второго аргумента требуется интегральное продвижение, а для f(T*,int) оператор преобразования вызывается для первого (благодаря @litb)

Вот тогда и начинается addr_impl_ref. Стандарт C ++ требует, чтобы последовательность преобразования могла содержать не более одного пользовательского преобразования.Оборачивая тип в addr_impl_ref и заставляя уже использовать последовательность преобразования, мы «отключаем» любой оператор преобразования, с которым поставляется тип.

Таким образом, выбирается перегрузка f(T&,long) (и Integral Promotionвыполняется).

Что происходит с любым другим типом?

Таким образом, перегрузка f(T&,long) выбрана, поскольку тип не соответствует параметру T*.

Примечание: из замечаний в файле относительно совместимости с Borland массивы не распадаются на указатели, а передаются по ссылке.

Что происходит в этомперегрузка?

Мы хотим избежать применения operator& к типу, так как он может быть перегружен.

Стандарт гарантирует, что reinterpret_cast может использоваться для этой работы (см. ответ @Matteo Italia: 5.2.10 / 10).

Boost добавляет некоторые тонкости с квалификаторами const и volatile, чтобы избежать предупреждений компилятора (и правильно используйте const_cast для их удаления).

  • Cast T& tochar const volatile&
  • Обрезать const и volatile
  • Применить оператор &, чтобы получить адрес
  • Привести обратно к T*

Жонглирование const / volatile - это немного чёрной магии, но оно упрощает работу (вместо обеспечения 4 перегрузок).Обратите внимание, что, поскольку T является безусловным, если мы передадим ghost const&, то T* будет ghost const*, таким образом, квалификаторы на самом деле не потеряны.

РЕДАКТИРОВАТЬ: Перегрузка указателя используется для указателя на функции, я несколько исправил приведенное выше объяснение.Я до сих пор не понимаю, почему это необходимо .

Следующий вывод идеона несколько суммирует это.

97 голосов
/ 27 июня 2011

По сути, вы можете переосмыслить объект как ссылку на символ, взять его адрес (не вызовет перегрузки) и привести указатель обратно к указателю вашего типа.

Повышение.Код AddressOf делает именно это, просто заботясь о квалификации volatile и const.

49 голосов
/ 27 июня 2011

Хитрость boost::addressof и реализация, предоставленная @Luc Danton, основана на магии reinterpret_cast; Стандарт в §5.2.10 ¶10 прямо заявляет, что

Выражение lvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» может быть явно преобразовано в тип «указатель на T2» используя reinterpret_cast. То есть эталонное приведение reinterpret_cast<T&>(x) имеет тот же эффект, что и преобразование *reinterpret_cast<T*>(&x) со встроенными операторами & и *. Результатом является lvalue, который ссылается на тот же объект, что и исходное lvalue, но с другим типом.

Теперь это позволяет нам преобразовать произвольную ссылку на объект в char & (с квалификацией cv, если ссылка является cv-квалифицированной), поскольку любой указатель может быть преобразован в (возможно, cv-квалифицированную) char * , Теперь, когда у нас есть char &, перегрузка оператора на объект больше не актуальна, и мы можем получить адрес с помощью встроенного оператора &.

Реализация boost добавляет несколько шагов для работы с cv-квалифицированными объектами: первый reinterpret_cast делается для const volatile char &, иначе простое приведение char & не будет работать для const и / или volatile ссылки (reinterpret_cast не может удалить const). Затем const и volatile удаляются с помощью const_cast, адрес берется с &, и завершается reinterpet_cast до "правильного" типа.

* const_cast необходим для удаления const / volatile, который мог быть добавлен к неконстантным / изменчивым ссылкам, но он не "вредит" тому, что было ссылкой const / volatile во-первых, потому что финальный reinterpret_cast повторно добавит квалификацию cv, если он был там на первом месте (reinterpret_cast не может удалить const, но может добавить его).

Что касается остальной части кода в addressof.hpp, кажется, что большая часть кода предназначена для обходных путей. static inline T * f( T * v, int ), по-видимому, необходим только для компилятора Borland, но его наличие создает необходимость в addr_impl_ref, иначе типы указателей будут захвачены этой второй перегрузкой.

Редактировать : различные перегрузки имеют разные функции, см. @ Matthieu M. отличный ответ .

Ну, я больше не уверен в этом; Я должен дополнительно изучить этот код, но сейчас я готовлю ужин :), я посмотрю его позже.

11 голосов
/ 27 июня 2011

Я видел реализацию addressof, которая делает это:

char* start = &reinterpret_cast<char&>(clyde);
ghost* pointer_to_clyde = reinterpret_cast<ghost*>(start);

Не спрашивайте меня, как это соответствует!

5 голосов
/ 27 июня 2011

Взгляните на boost :: addressof и его реализацию.

...