2011-02-08 16 views
7

È possibile avere una variabile membro, che sarebbe in grado di calcolare il puntatore all'oggetto contenitore dal puntatore a se stesso (nel suo metodo)?Variabile membro della classe C++ che conosce il proprio offset

Diamo un'interfaccia chiamata estera avvolto in API simili:

template <typename Class, MethodId Id, typename Signature> 
class MethodProxy; 

template <typename Class, MethodId Id, typename ReturnT, typename Arg1T> 
class MethodProxy<Class, Id, ReturnT()(Arg1T) { 
    public: 
    ReturnT operator()(Class &invocant, Arg1T arg1); 
}; 

e similmente per altri numeri di argomenti da 0 a N. Per ogni classe sul lato straniero, una classe C++ è dichiarata con alcuni tratti e questo modello usa quei tratti (e più tratti per i tipi di argomento) per trovare e invocare il metodo straniero. Questo può essere usato come:

Foo foo; 
MethodProxy<Foo, barId, void()(int)> bar; 
bar(foo, 5); 

Ora quello che vorrei fare è definire Foo in modo tale, che io possa chiamare come:

Foo foo; 
foo.bar(5); 

senza ripetere la firma più volte. (ovviamente creare un membro statico e avvolgere la chiamata in un metodo è semplice, giusto). Beh, in effetti, che è ancora facile:

template <typename Class, MethodId Id, typename Signature> 
class MethodMember; 
template <typename Class, MethodId Id, typename ReturnT, typename Arg1T> 
class MethodMember<Class, Id, ReturnT()(Arg1T) { 
    MethodProxy<Class, Id, Signature> method; 
    Class &owner; 
    public: 
    MethodMember(Class &owner) : owner(owner) {} 
    ReturnT operator()(Arg1T arg1) { return method(owner, arg1); } 
}; 

Che però significa che l'oggetto finirà che contiene molte copie di puntatore a se stesso. Quindi sto cercando un modo per rendere queste istanze in grado di calcolare il puntatore del proprietario da this e alcuni argomenti del modello aggiuntivi.

Stavo pensando lungo le linee di

template <typename Class, size_t Offset, ...> 
class Member { 
    Class *owner() { 
     return reinterpret_cast<Class *>(
      reinterpret_cast<char *>(this) - Offset); 
    } 
    ... 
}; 
class Foo { 
    Member<Foo, offsetof(Foo, member), ...> member; 
    ... 
}; 

ma questo si lamenta che Foo è tipo incompleto al punto.

Sì, so che si suppone che offsetof funzioni solo per i tipi "POD", ma in pratica per qualsiasi membro non virtuale, che sarà, funziona. Allo stesso modo ho cercato di passare da pointer a to (that) -member (usando dummy base-class) in quell'argomento, ma anche questo non funziona.

Nota: se questo ha funzionato, potrebbe anche essere usato per implementare proprietà C# -like che delegano ai metodi della classe contenente.

So come eseguire i metodi wrapper menzionati sopra con boost.preprocessor, ma gli elenchi di argomenti dovrebbero essere specificati in una forma strana. So come scrivere macro per generare wrapper generici tramite template, ma questo probabilmente darebbe una scarsa diagnostica. Sarebbe anche banale se le chiamate potessero assomigliare a foo.bar()(5). Ma mi piacerebbe sapere se un trucco intelligente sarebbe possibile (in più solo un trucco così intelligente sarebbe probabilmente utilizzabile anche per le proprietà).

Nota: il tipo di membro non può essere effettivamente specializzato né su un puntatore a esso né su un offset, poiché il tipo deve essere noto prima di poter assegnare quell'offset. Questo perché il tipo può influenzare l'allineamento richiesto (considerare la specializzazione esplicita/parciale).

+0

ho letto che molte volte, ma ancora non si ottiene ciò che si vuole fare, vuoi un generico di classe * proprietà * che è a conoscenza di a cosa serve - se sì, perché dovrebbe sapere cosa lo possiede? Dovrei immaginare che tutto ciò di cui una proprietà ha veramente bisogno è la capacità di accettare un valore e restituire il valore? – Nim

+1

Non capisco neanche ..Cosa stai cercando di fare? – mfontanini

+0

@Nim: Sì, voglio una classe generica di "proprietà" che sia a conoscenza di cosa lo possiede. Per la proprietà è necessario se il valore della proprietà deve essere * calcolato *. Nel mio caso è tuttavia un funtore che deve passare il puntatore al proprietario al metodo sottostante. –

risposta

6

fare una domanda è il modo migliore per realizzare la risposta, quindi questo è dove ho ottenuto:

L'offset non può essere un argomento modello, perché il tipo deve essere noto prima che l'offset possa essere calcolato. Quindi deve essere restituito da una funzione dell'argomento. Aggiungiamo un tipo di tag (struct fittizio) e una put in una funzione overload nel proprietario o direttamente nel tag. In questo modo possiamo definire tutto ciò di cui abbiamo bisogno in un posto (usando una macro). Il seguente codice compila bene con gcc 4.4.5 e stampe corretto puntatore per tutti i membri:

#include <cstddef> 
#include <iostream> 

using namespace std; 

(solo preambolo per renderlo veramente compilazione)

template <typename Owner, typename Tag> 
struct offset_aware 
{ 
    Owner *owner() 
    { 
     return reinterpret_cast<Owner *>(
      reinterpret_cast<char *>(this) - Tag::offset()); 
    } 
}; 

Questo è ciò che è necessario per rendere l'oggetto a conoscenza del proprio offset. La proprietà o il functor o qualche altro codice possono essere aggiunti liberamente per renderlo utile. Ora abbiamo bisogno di dichiarare alcune cose in più con il membro stesso, quindi cerchiamo di definire questa macro:

#define OFFSET_AWARE(Owner, name) \ 
    struct name ## _tag { \ 
     static ptrdiff_t offset() { \ 
      return offsetof(Owner, name); \ 
     } \ 
    }; \ 
    offset_aware<Owner, name ## _tag> name 

Questo definisce struttura come il tag e mette in una funzione che restituisce l'offset richiesto. Di ciò definisce il membro dei dati stesso.

Nota che il membro deve essere pubblico come definito qui, ma è possibile aggiungere facilmente una dichiarazione "amico" per il tag di supporto proprietà protette e private. Ora usiamolo.

struct foo 
{ 
    int x; 
    OFFSET_AWARE(foo, a); 
    OFFSET_AWARE(foo, b); 
    OFFSET_AWARE(foo, c); 
    int y; 
}; 

Semplice, non è vero?

int main() 
{ 
    foo f; 

    cout << "foo f = " << &f << endl 
     << "f.a: owner = " << f.a.owner() << endl 
     << "f.b: owner = " << f.b.owner() << endl 
     << "f.c: owner = " << f.c.owner() << endl; 
    return 0; 
} 

Stampa lo stesso valore del puntatore su tutte le linee. Lo standard C++ non consente ai membri di avere dimensione 0, ma avranno solo la dimensione del loro contenuto effettivo o 1 byte se sono altrimenti vuoti rispetto a 4 o 8 byte (a seconda della piattaforma) per un puntatore.

+0

Probabilmente dovresti notare che se qualcuno eredita una classe polimorfica da 'foo' questa soluzione si interromperà. –

+0

@ MarkB: Sarà? Indipendentemente da come 'foo' sia derivato, il membro è ancora allo stesso offset dalla sottostanza' foo', quindi il 'owner()' restituisce il puntatore a quella sostanza secondaria, che è esattamente dove ci si aspetta un 'foo *' punto. –

+0

@JanHudec, è brillante, evento mezzo decennio dopo. Grazie. – Kumputer

0

Supponendo che le chiamate abbiano effettivamente bisogno di un riferimento all'oggetto contenitore, è sufficiente memorizzare il riferimento al proprietario. A meno che non si disponga di prove specifiche del profilo di memoria che causano un aumento significativo della memoria per memorizzare i riferimenti aggiuntivi, è sufficiente farlo in modo ovvio.

+1

Hai letto la domanda? Questo è esattamente ciò che fa l'opzione intermedia e di quanto ho chiesto se c'è un modo per ottenere senza quel riferimento. –

+1

@Jan Hudec Perché pensi che i riferimenti extra siano un problema reale che deve essere risolto? La mia prima lettura della domanda è che potrebbe non esserci un problema reale qui. –

+2

Bene, la domanda richiede specificamente una soluzione che non li abbia. Non è un problema con loro o no — Io li escludo specificamente perché altrimenti la questione è banale e non vale la pena essere discussa. –

1

Per ora, ecco uno soluzione MS-specifica, ancora pensando come renderla più generale

#include <stdio.h> 

#define offs(s,m) (size_t)&(((s *)0)->m) 
#define Child(A,B,y) \ 
    __if_exists(X::y) { enum{ d_##y=offs(X,y) }; } \ 
    __if_not_exists(X::y) { enum{ d_##y=0 }; } \ 
    B<A,d_##y> y; 

template <class A, int x> 
struct B { 
    int z; 
    void f(void) { 
    printf("x=%i\n", x); 
    } 
}; 

template< class X > 
struct A { 
    int x0; 
    int x1; 
    Child(A,B,y); 
    Child(A,B,z); 
}; 

typedef A<int> A0; 

typedef A<A0> A1; 

int main(void) { 
    A1 a; 
    a.y.f(); 
    a.z.f(); 
} 
+0

L'__if_exists/__ if_not_exists non dovrebbe essere necessario. È un errore se non esiste comunque. E questa è l'unica cosa specifica per MS lì, giusto? –

+0

Ho fatto un piccolo ritocco (in pratica sostituendo 'offs' con' offsetof' da '', ma gcc si rifiuta di compilarlo perché non consente il riferimento ai membri prima che siano dichiarati (tranne che nei corpi del metodo). C++ deve tagliare alcuni angoli, perché l'allineamento richiesto potrebbe in generale dipendere dall'argomento del template –

+0

L'idea è che la classe sia definita la prima volta senza offset, quindi definita nuovamente usando gli offset dalla prima istanza È facile da fare con 2x #include + macro ridefinizione, ma questo è scomodo.Tuttavia, non so ancora come impostare SFINAE per membri della classe inesistenti (ridefinendo operatore-> o -> * sembrava promettente, ma non ha funzionato) Quindi non funzionerà senza __if_exists. – Shelwien

2

1) C'è un prolungamento gcc che sembrava montaggio:

enum{ d_y = __builtin_choose_expr(N,offsetof(X,y),0) }; 

Ma non ha funzionato come previsto, anche se manuale dice
"la funzione built-in non valutare l'espressione che non era scelto "

2) i puntatori membri sembravano interessanti, ad es. offsetof può essere definito in questo modo:

template< class C, class T > 
int f(T C::*q) { 
    return (int)&((*(C*)0).*q); 
} 

Ma io ancora non ha trovato un modo per trasformare questo in constexpr.

3) Per ora, ecco un'altra versione:

#include <stdio.h> 

#pragma pack(1) 

template <class A, int x> 
struct B { 
    int z; 
    void f(void) { 
    printf("x=%i\n", x); 
    } 
}; 

#define STRUCT(A) template< int N=0 > struct A { 
#define CHILD(A, N, B, y) }; template<> struct A<N> : A<N-1> \ 
    { B<A<N>,sizeof(A<N-1>)> y; 
#define STREND }; 

STRUCT(A) 
    int x0; 
    int x1; 
    CHILD(A,1, B, y); 
    short x2; 
    CHILD(A,2, B, z); 
    char x3; 
STREND 

typedef A<2> A1; 

int main(void) { 
    A1 a; 
    a.y.f(); 
    a.z.f(); 
} 
+0

L'espressione non selezionata non è rilevante. Il membro esiste sempre e l'offset del ramo deve essere scelto. Non può essere valutato in tempo - è necessaria una funzione (in linea). –

+0

Non c'è ancora alcuna prova che non esista un modo di compilazione in tempo reale (in realtà ho già pubblicato 2). – Shelwien

Problemi correlati