2012-08-27 7 views
7

Ich möchte den Offset einer Standard-Layout-Elementvariable erhalten, wenn mit einem poiner zu dieser Variable bereitgestellt wird. Ich kann offsetof nicht verwenden, da ich einen Zeiger und keinen Namen habe. Der aktuelle Code, den ich habe, sieht in etwa so aus, und ich frage mich, ob es eine standardkonforme Möglichkeit gibt, die dummy Variable loszuwerden.Offset von Member-Zeiger ohne temporäre Instanz

template<class T> 
struct { 
    ptrdiff_t get_offset(int (T::*mem)) 
    { 
    T dummy; 
    return reinterpret_cast<char*>(&(dummy.*mem)) 
     - reinterpret_cast<char*>(&dummy); 
    } 
}; 

Diese Funktion sollte nur mit int Membervariable (dies ist beabsichtigt) aufrufbar sein.

Ich bin ziemlich sicher, dass der Compiler nicht tatsächlich die dummy Variable erstellt, aber es wäre immer noch schön, wenn ich es loswerden könnte. Ich kann keinen Nullzeiger verwenden, da das Dereferenzieren von Null nicht definiert ist (obwohl es wahrscheinlich bei allen gängigen Compilern funktioniert). Eine C++ 03-Lösung wäre gut, oder eine C++ 11-Lösung ist ebenfalls von Interesse (kann aber von mir nicht mehr verwendet werden).

HINWEIS: Ich bin mir bereits bewusst, dass dies nur Normenkonform ist, ist T ein Standard-Layout-Typ.

+0

Der Rückgabetyp sollte "ptrdiff_t" sein, nehme ich an, und Sie sollten 'std :: distance' verwenden. Und die Funktion sollte "statisch" sein. –

+0

@KerrekSB, ja. Ich kompiliere mit vollständigen/zusätzlichen Warnungen in GCC, aber ich denke 'size_t == ptrdiff_t', also keine Warnungen. –

+0

'ptrdiff_t' ist signiert ... wie auch immer, der' Dummy' sollte auch statisch sein, nehme ich an. –

Antwort

2

Ich befürchte, dass keine standardkonforme Lösung existiert, die OP-Anforderungen erfüllt.

Ich kann ein paar nicht konforme geben.

template<class T> 
    size_t get_offset(int (T::*mem)) 
    { 
    return reinterpret_cast<char*>(&(((T*)nullptr)->*mem))-reinterpret_cast<char*>(nullptr); 
    } 

Es ist komisch, aber die folgenden Arbeiten in VC2010, die Nutzung von offsetof ein Makro zu sein.

template<class T> 
    size_t get_offset(int (T::*mem)) 
    { 
    return offsetof(T, *mem); 
    } 
+0

Sie denerenzieren einen Null-Zeiger über die '-> *' Operator. Betrachten Sie das Äquivalent '(* (T *) nullptr). * Mem' –

+0

Sie deneferenzieren immer noch einen Nullzeiger. Nur weil 'offsetof' es tut, heißt das nicht, dass es nicht UB ist. –

+0

@PeterAlexander: Ja, ich weiß ... Ich habe Angst, dass es keine Lösung gibt, die OP-Anforderungen erfüllt. Entweder ist eine Dummy-Variable oder dereferenzierend NULL erforderlich. – Andrey

7

Wie wäre:

template<class T> 
struct { 
    ptrdiff_t get_offset(int (T::*mem)) 
    { 
    union { 
     int used; 
     T unused; 
    } dummy; 
    return reinterpret_cast<char*>(&(dummy.unused.*mem)) 
     - reinterpret_cast<char*>(&dummy.unused); 
    } 
}; 

Die Adresse eines Gewerkschaftsmitglied nicht auf dem Gewerkschaftsmitglied abhängig gebaut. Funktioniert bereits in C++ 03, aber nur für PODs.

+0

Also diese Version beseitigt grundsätzlich jeden Aufruf von 'T :: T()' und 'T: ~ T()'. Es hat immer noch einen Dummy, aber die abstrakte Maschine führt weniger Code aus. –

+0

Nun, offensichtlich brauchen Sie die Erinnerung, sonst können Sie die Zeiger nicht bilden. – MSalters

+0

Dies ist immer noch UB, da Sie eine Komponente einer "Union" verwenden, die an der Verwendungsstelle nicht aktiv war. Also, wie ist es besser als 'reinterpret_cast (nullptr)'? Es ist schlimmer, dass Sie am Ende einen Haufen Stapelraum für das 'T' schaffen. Es könnte ein System geben, auf dem das funktioniert, aber das "nullptr" nicht, aber umgekehrt könnte das auch stimmen. – Yakk

1

So wie etwa:

template<class T> 
struct { 
    ptrdiff_t get_offset(int (T::*mem)) 
    { 
     return 
     (&reinterpret_cast<const char&>( 
      reinterpret_cast<const T*>(1)->*mem) 
      - reinterpret_cast<const char*>(1)  ); 
    } 
}; 

..?

Dies vermeidet sowohl die Verwendung eines Dummy UND NULL Dererenzierung. Es funktioniert bei allen Compilern, die ich ausprobiert habe. Die cast-to-char-Referenz und dann die Adresse (anstatt die Adresse zu nehmen und dann in den char-Zeiger zu werfen) mag ungewöhnlich erscheinen, vermeidet jedoch eine Warnung/einen Fehler bei einigen Compilern.