wake-up-neo.com

Wie kann ich die Adresse eines Objekts zuverlässig ermitteln, wenn Operator & überlastet ist?

Betrachten Sie das folgende Programm:

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 :'( 
}

Wie erhalte ich die Adresse von clyde?

Ich suche nach einer Lösung, die für alle Arten von Objekten gleich gut funktioniert. Eine C++ 03-Lösung wäre schön, aber ich interessiere mich auch für C++ 11-Lösungen. Vermeiden wir nach Möglichkeit implementierungsspezifisches Verhalten.

Mir ist bekannt, dass C++ 11 std::addressof Funktionsvorlage, aber ich bin nicht daran interessiert, sie hier zu verwenden: Ich möchte verstehen, wie ein Standardbibliotheksimplementierer diese Funktionsvorlage implementieren könnte.

167
James McNellis

Update: In C++ 11 kann man std::addressof Anstelle von boost::addressof Verwenden.


Kopieren wir zuerst den Code aus Boost, ohne dass der Compiler die Bits umgeht:

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 );
}

Was passiert, wenn wir einen Verweis auf die Funktion übergeben?

Hinweis: addressof kann nicht mit einem Zeiger auf eine Funktion verwendet werden

Wenn in C++ void func(); deklariert ist, ist func eine Referenz auf eine Funktion, die kein Argument verwendet und kein Ergebnis zurückgibt. Dieser Verweis auf eine Funktion kann trivial in einen Zeiger auf eine Funktion umgewandelt werden - von @Konstantin: Gemäß 13.3.3.2 sind sowohl T & Als auch T * Für Funktionen nicht unterscheidbar. Die erste ist eine Identitätskonvertierung und die zweite ist eine Funktion-zu-Zeiger-Konvertierung, die beide den Rang "Exakte Übereinstimmung" haben (13.3.3.1.1, Tabelle 9).

Der Verweis auf Funktion durchläuft addr_impl_ref, Es besteht eine Mehrdeutigkeit in der Überladungsauflösung für die Wahl von f, die dank des Dummy-Arguments 0, Das zuerst ein int ist und zu einem long (Integral Conversion) befördert werden könnte.

Wir geben also einfach den Zeiger zurück.

Was passiert, wenn wir einen Typ mit einem Konvertierungsoperator übergeben?

Wenn der Konvertierungsoperator einen T* Ergibt, haben wir eine Mehrdeutigkeit: Für f(T&,long) ist eine integrale Beförderung für das zweite Argument erforderlich, während für f(T*,int) der Konvertierungsoperator aufgerufen wird die erste (danke an @litb)

Dann setzt addr_impl_ref Ein. Der C++ - Standard schreibt vor, dass eine Konvertierungssequenz höchstens eine benutzerdefinierte Konvertierung enthalten darf. Indem wir den Typ in addr_impl_ref Einschließen und die Verwendung einer Konvertierungssequenz erzwingen, "deaktivieren" wir jeden Konvertierungsoperator, mit dem der Typ geliefert wird.

Somit wird die Überladung f(T&,long) ausgewählt (und die Integral-Promotion durchgeführt).

Was passiert bei jedem anderen Typ?

Daher wird die Überladung f(T&,long) ausgewählt, da der Typ dort nicht mit dem Parameter T* Übereinstimmt.

Hinweis: Aus den Hinweisen in der Datei zur Borland-Kompatibilität geht hervor, dass Arrays nicht zu Zeigern zerfallen, sondern als Referenz übergeben werden.

Was passiert bei dieser Überladung?

Wir möchten vermeiden, operator& Auf den Typ anzuwenden, da dieser möglicherweise überladen wurde.

Der Standard garantiert, dass reinterpret_cast Für diese Arbeit verwendet werden kann (siehe Antwort von @Matteo Italia: 5.2.10/10).

Boost fügt einige Feinheiten mit den Qualifikationsmerkmalen const und volatile hinzu, um Compiler-Warnungen zu vermeiden (und entfernt sie ordnungsgemäß mit einem const_cast).

  • Wirf T& In char const volatile& Um
  • Entferne die const und volatile
  • Wenden Sie den Operator & An, um die Adresse zu übernehmen
  • Zurück zu einem T*

Das Jonglieren mit const/volatile ist ein bisschen schwarze Magie, vereinfacht aber die Arbeit (anstatt 4 Überladungen bereitzustellen). Beachten Sie, dass, da T nicht qualifiziert ist, wenn wir einen ghost const& Übergeben, T*ghost const* Ist, sodass die Qualifikationsmerkmale nicht wirklich verloren gegangen sind.

EDIT: Die Zeigerüberladung wird für Zeiger auf Funktionen verwendet, ich habe die obige Erklärung etwas geändert. Ich verstehe immer noch nicht, warum es notwendig ist.

Das folgende ideone output fasst dies etwas zusammen.

98
Matthieu M.

Grundsätzlich können Sie das Objekt als Verweis auf ein Zeichen neu interpretieren, seine Adresse übernehmen (nicht die Überladung aufrufen) und den Zeiger auf einen Zeiger Ihres Typs zurücksetzen.

Der Boost.AddressOf-Code erledigt genau das und kümmert sich zusätzlich um die Qualifikation volatile und const.

97
Konrad Rudolph

Ich habe eine Implementierung von addressof gesehen.

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

Frag mich nicht, wie konform das ist!

11
Luc Danton

Schauen Sie sich boost :: addressof und dessen Implementierung an.