Как я могу надежно получить адрес объекта, когда оператор & перегружен?
Рассмотрим следующую программу:
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);
Не спрашивайте меня, как это соответствует!