2013-06-08 14 views
5

È legale in C++ 11? Compilare con l'ultimo compilatore Intel e sembra funzionare, ma ho la sensazione che sia un colpo di fortuna.Posizionamento nuovo basato sul modello sizeof()

class cbase 
     { 
     virtual void call(); 
     }; 

template<typename T> class functor : public cbase 
    { 
    public: 
     functor(T* obj, void (T::*pfunc)()) 
      : _obj(obj), _pfunc(pfunc) {} 

     virtual void call() 
      { 
      (_obj)(*_pfunc)(); 
      } 
    private: 
     T& _obj; 
     void (T::*_pfunc)();    
     //edited: this is no good: 
     //const static int size = sizeof(_obj) + sizeof(_pfunc); 
    }; 

class signal 
    { 
    public: 
     template<typename T> void connect(T& obj, void (T::*pfunc)()) 
      { 
      _ptr = new (space) functor<T>(obj, pfunc); 
      } 
    private: 
     cbase* _ptr; 
     class _generic_object {}; 
     typename aligned_storage<sizeof(functor<_generic_object>), 
      alignment_of<functor<_generic_object>>::value>::type space; 
     //edited: this is no good: 
     //void* space[(c1<_generic_object>::size/sizeof(void*))]; 

    }; 

In particolare mi chiedo se void* space[(c1<_generic_object>::size/sizeof(void*))]; realmente sta andando dare il formato corretto per gli oggetti membro di C1 (_obj e _pfunc). (Non lo è).

EDIT: Così, dopo qualche ricerca più sembrerebbe che il seguente sarebbe (più?) Corretto:

typename aligned_storage<sizeof(c1<_generic_object>), 
    alignment_of<c1<_generic_object>>::value>::type space; 

Tuttavia su ispezione del assembly generato, utilizzando nuova collocazione con questo spazio sembra inibire la compilatore di ottimizzazione via la chiamata al 'nuovo' (che sembrava accadere durante l'utilizzo di solo regolare '_ptr = new c1;'

EDIT2: Cambiato il codice per fare un po 'più chiaro le intenzioni

+0

Come si suppone che si possa compilare senza fornire un inizializzatore per un membro di riferimento in 'c1'? C'è qualche importanza specifica in quel membro di riferimento o no? – AnT

+0

Inoltre, qual è l'idea alla base del calcolo della dimensione della memoria attraverso 'sizeof (_obj)', che valuta la dimensione dell'oggetto completo (cioè 'sizeof T'), ma poi costruisci un oggetto' c1 'in quel luogo, che fisicamente contiene solo un * riferimento * a 'some_type_t' (implementato come un puntatore)? – AnT

+0

Non so se compila o meno, è un esempio molto semplificato che ho fatto per postare qui per tagliare alle parti rilevanti. Sto scrivendo una classe signal/callback e desidero eliminare l'allocazione dinamica della memoria, se possibile. –

risposta

3

.fornirà la somma delle dimensioni dei membri, ma potrebbe non corrispondere alla dimensione della classe contenente tali membri. Il compilatore è libero di inserire il padding tra i membri o dopo l'ultimo membro. In quanto tale, sommando le dimensioni dei membri si approssima il più piccolo possibile dell'oggetto, ma non necessariamente fornisce la dimensione di un oggetto con tali membri.

In effetti, le dimensioni di un oggetto possono variare in base non solo al tipo di membri, ma anche al loro ordine. Per esempio:

struct A { 
    int a; 
    char b; 
}; 

vs:

struct B { 
    char b; 
    int a; 
}; 

In molti casi, A sarà più piccolo di B. In A, non ci sarà in genere padding tra a e b, ma in B, ci sarà spesso qualche padding (ad esempio, con un int per 4 byte, ci saranno spesso 3 byte di padding tra e a).

In tal caso, lo space potrebbe non contenere abbastanza spazio per contenere l'oggetto che si sta tentando di creare in init.

+0

Questo è un buon punto. Idealmente, potrei memorizzare gli oggetti direttamente in c2, ma poiché non conosco T finché non viene chiamata una funzione membro, non posso farlo. –

1

Penso che tu sia stato fortunato; La risposta di Jerry sottolinea che potrebbero esserci problemi di padding. Quello che penso che tu abbia è una classe non virtuale (cioè, non vtable), con essenzialmente due puntatori (sotto il cofano).

A parte questo, l'aritmetica: (c1<_generic_object>::size/sizeof(void*)) è viziato perché sarà troncare se size è non un multiplo di sizeof(void *). Si avrebbe bisogno di qualcosa di simile a:

((c1<_generic_object>::size + sizeof(void *) - 1)/sizeof(void *))

1

Questo codice non ha nemmeno arrivare a problemi di imbottitura, perché ha alcuni di quelli più immediati.

La classe di modello c1 è definita per contenere un membro T &_obj di tipo di riferimento. L'applicazione di sizeof a nella dimensione di c1 verrà valutata alla dimensione di T, non alla dimensione del membro di riferimento stesso.Non è possibile ottenere la dimensione fisica di un riferimento in C++ (almeno direttamente). Nel frattempo, qualsiasi oggetto reale di tipo c1<T> conterrà fisicamente un riferimento a T, che in genere viene implementato in casi come un puntatore "sotto il cofano".

Per questo motivo è completamente chiaro per me perché il valore di c1<_generic_object>::size viene utilizzata come misura di memoria necessaria per la costruzione in ritmo di un oggetto reale di tipo c1<T> (per qualsiasi T). Non ha alcun senso. Queste dimensioni non sono affatto correlate.

Per pura fortuna, le dimensioni di una classe vuota _generic_object potrebbero avere lo stesso valore (o maggiore) delle dimensioni di un'implementazione fisica di un membro di riferimento. In tal caso il codice assegnerà una quantità sufficiente di memoria. Si potrebbe persino sostenere che l'uguaglianza sizeof(_generic_object) == sizeof(void *) "di solito" valga nella pratica. Ma sarebbe solo una coincidenza completamente arbitraria senza una base significativa.

Questo sembra addirittura un'aringa rossa inserita deliberatamente nel codice allo scopo di pura offuscazione.

P.S. In GCC sizeof di una classe vuota in realtà si valuta su 1, non su alcuna dimensione "allineata". Ciò significa che la suddetta tecnica è garantita per inizializzare c1<_generic_object>::size con un valore troppo piccolo. In particolare, in GCC a 32 bit il valore di c1<_generic_object>::size sarà 9, mentre le dimensioni effettive di qualsiasi c1<some_type_t> saranno 12 byte.

Problemi correlati