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


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

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 ' s адрес?

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

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

5 164

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

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

The ссылка на функцию проходит через 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) перегрузка выбрана, потому что там тип не соответствует

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

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

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

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

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

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

The 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, в противном случае типы указателей будут пойманы этой второй перегрузкой.

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

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

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

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

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

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