2012-08-27 8 views
7

Chciałbym uzyskać przesunięcie standardowej zmiennej składowej układu, jeśli podano ją poinsem dla tej zmiennej. Nie mogę użyć offsetof, ponieważ mam wskaźnik, a nie nazwę. Obecny kod Mam coś takiego wygląda i zastanawiam się, czy istnieje zgodny z normami sposób, aby pozbyć się zmiennej dummy.Przesunięcie od wskaźnika członka bez tymczasowej instancji

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

Funkcja ta powinna być tylko wymagalne z int członkowskich zmiennych punktów (jest to celowe).

Jestem całkowicie pewien, że kompilator nie tworzy zmiennej dummy, ale nadal byłoby miło, gdybym mógł się jej pozbyć. Nie mogę użyć wskaźnika pustego, ponieważ dereferencja null nie jest zdefiniowana (chociaż prawdopodobnie działa na wszystkich typowych kompilatorach). Rozwiązanie C++ 03 byłoby dobre, lub rozwiązanie C++ 11 jest również interesujące (ale nie jest już użyteczne dla mnie).

UWAGA: Mam już świadomość, że jest to zgodne tylko ze standardami, T to standardowy typ układu.

+0

Typ powrotu powinien być 'ptrdiff_t', tak przypuszczam, i powinieneś użyć' std :: distance'. A funkcja powinna być "statyczna". –

+0

@KerrekSB, tak. Kompiluję z pełnymi/dodatkowymi ostrzeżeniami w GCC, ale myślę, że 'size_t == ptrdiff_t', więc nie ma ostrzeżeń. –

+0

'ptrdiff_t' jest podpisane ... w każdym razie' manekin' również powinien być statyczny, jak sądzę. –

Odpowiedz

2

Obawiam się, że nie istnieje żadne zgodne z normami rozwiązanie, które spełnia wymagania PO.

Mogę podać kilka niezgodnych z nimi.

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

To zabawne, ale następujące prace w VC2010, wykorzystując offsetof bycia makro.

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

Wywołujesz zerowy wskaźnik za pomocą '-> *' operator. Rozważmy odpowiednik '(* (T *) nullptr). * Mem' –

+0

Nadal wyłuskujesz zerowy wskaźnik. Tylko dlatego, że "offsetof" to robi, nie oznacza, że ​​nie jest to UB. –

+0

@PeterAlexander: Tak, wiem ... Obawiam się, że nie istnieje żadne rozwiązanie spełniające wymagania PO. Wymagana jest albo fikcyjna zmienna albo dereferencyjna wartość NULL. – Andrey

7

Jak o:

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

Adres członka unii nie zależy na członie związkowej w trakcie budowy. Działa już w C++ 03, ale tylko dla POD.

+0

Tak więc ta wersja w zasadzie eliminuje wszelkie wywołania 'T :: T()' i 'T: ~ T()'. Nadal ma manekina, ale abstrakcyjna maszyna wykonuje mniej kodu. –

+0

Cóż, oczywiście potrzebujesz pamięci, inaczej nie możesz utworzyć wskaźników. – MSalters

+0

To nadal jest UB, ponieważ używasz składnika "union", który nie był aktywny w momencie użycia. Jako takie, jak jest lepsze niż 'reinterpret_cast (nullptr)'? Gorzej jest z tym, że w efekcie powstaje sporo miejsca na stos do 'T'. Może istnieć system, w którym to działa, ale 'nullptr' nie, ale na odwrót również może być prawdą. – Yakk

1

Tak jak o:

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

..?

Pozwala to uniknąć użycia zarówno pustej, jak i zerowej zerowej wartości. Działa na wszystkich kompilatorach, których próbowałem. Odsunięcie do odwołania do znaku, a następnie pobranie adresu (zamiast odebrania adresu, a następnie przeniesienia do wskaźnika) może wydawać się niezwykłe, ale pozwala uniknąć ostrzeżenia/błędu w niektórych kompilatorach.

Powiązane problemy