Как я могу надежно получить адрес объекта, когда оператор & перегружен?

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

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. Если возможно, давайте избегать поведения, специфичного для реализации.

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

5 ответов

Решение

Обновление: в 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 нельзя использовать с указателем на функцию

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

Ссылка на функцию pass through 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) Перегрузка выбрана (и Интегральное продвижение выполнено).

Что происходит для любого другого типа?

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

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

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

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

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

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

  • В ролях T& в char const volatile&
  • Раздеть const а также volatile
  • Применить & оператор, чтобы взять адрес
  • Откинь назад к T*

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

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

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

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

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

Хитрость позади 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. Отличный ответ.

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

Я видел реализацию addressof сделай это:

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

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

Посмотрите на boost::addressof и его реализацию.

Другие вопросы по тегам