2010-09-07 13 views
7

rimosso il tag C, visto che quello era causando qualche confusione (che non avrebbe dovuto essere lì per cominciare;. Scusa per gli eventuali disagi ci C rispondere come sempre il benvenuto se :)Creazione di oggetti di dimensioni dinamicamente

In alcune cose che ho fatto, ho trovato la necessità di creare oggetti che hanno una dimensione dinamica e una dimensione statica, dove la parte statica è i membri di base dell'oggetto, la parte dinamica è comunque un array/buffer aggiunto direttamente sulla classe, mantenendo la memoria contigua, diminuendo così la quantità di allocazioni necessarie (questi sono oggetti non riallocabili) e riducendo la frammentazione (anche se come un lato negativo, potrebbe essere più difficile trovare un blocco di dimensioni abbastanza grandi, tuttavia questo è molto più raro - se dovrebbe anche o ccur affatto - che frammentare l'heap. Questo è utile anche su dispositivi embedded in cui la memoria è di livello premium (tuttavia non faccio nulla per i dispositivi embedded al momento), e cose come std :: string devono essere evitate, o non possono essere usate come nel caso di sindacati banali.

In generale il modo in cui mi piacerebbe andare su questo sarebbe quello di (ab) usare malloc (std :: string non viene utilizzato di proposito, e per vari motivi):

struct TextCache 
{ 
    uint_32 fFlags; 
    uint_16 nXpos; 
    uint_16 nYpos; 
    TextCache* pNext; 
    char pBuffer[0]; 
}; 

TextCache* pCache = (TextCache*)malloc(sizeof(TextCache) + (sizeof(char) * nLength)); 

Questo però non lo fa siedo troppo bene con me, come in primo luogo desidero fare questo ambiente utilizzando nuova, e quindi in C++, e in secondo luogo, sembra orribile: P

passo così la prossima era una variante su modelli C++:

template <const size_t nSize> struct TextCache 
{ 
    uint_32 fFlags; 
    uint_16 nXpos; 
    uint_16 nYpos; 
    TextCache<nSize>* pNext; 
    char pBuffer[nSize]; 
}; 

Questo h ha owever il problema che memorizza un puntatore a un oggetto di dimensioni variabile diventa 'impossibile', così poi il successivo lavoro intorno:

class DynamicObject {}; 
template <const size_t nSize> struct TextCache : DynamicObject {...}; 

Questo tuttavia richiede ancora colata, e avendo puntatori a DynamicObject dappertutto diventa ambiguo quando più che un oggetto di dimensioni dinamiche deriva da esso (sembra anche orribile e può soffrire di un bug che costringe le classi vuote ad avere ancora una dimensione, anche se questo è probabilmente un bug arcaico, estinto ...).

poi c'è stato questo:

class DynamicObject 
{ 
    void* operator new(size_t nSize, size_t nLength) 
    { 
     return malloc(nSize + nLength); 
    } 
}; 

struct TextCache : DynamicObject {...}; 

che sembra molto meglio, ma potrebbero interferire con gli oggetti che hanno già sovraccarichi di nuovo (che potrebbe anche influenzare nuova collocazione ...).

Infine mi si avvicinò con nuova collocazione abusando:

inline TextCache* CreateTextCache(size_t nLength) 
{ 
    char* pNew = new char[sizeof(TextCache) + nLength]; 
    return new(pNew) TextCache; 
} 

Questo però è probabilmente la peggiore idea finora, per un bel paio di motivi.

Quindi ci sono modi migliori per farlo? o sarebbe una delle versioni precedenti essere migliore, o almeno migliorabile? Sta addirittura prendendo in considerazione la pratica di programmazione sicura e/o cattiva?


come ho detto sopra, sto cercando di evitare doppie assegnazioni, perché questo non dovrebbe aver bisogno di 2 assegnazioni, e causare questo rende la scrittura (serializzazione) queste cose ai file molto più facile. L'unica eccezione al requisito di doppia allocazione che ho è quando il suo overhead è praticamente zero. l'unica causa in cui mi sono imbattuto è dove alloco sequenzialmente la memoria da un buffer fisso (using this system, che mi è venuta in mente), ma è anche un'eccezione speciale per evitare la copia superfluo.

+1

C'è qualche motivo per cui non si desidera utilizzare STL? –

+1

@Kornel: forse a causa del tag 'C' - ma gli esempi suggeriscono che forse il tag C è superfluo (anche se la mia risposta si concentra su C). –

+0

@Kornel: alcune delle cose che ho in mente non possono usare STL, a causa di esse a) usando allocatori che non sono amichevoli con STL, o b) sono allocatori stessi, e l'uso di STL potrebbe sconfiggere scopo completo della loro esistenza. – Necrolis

risposta

7

Mi piacerebbe un compromesso con il concetto DynamicObject. Tutto ciò che non dipende dalle dimensioni va nella classe base.

struct TextBase 
{ 
    uint_32 fFlags; 
    uint_16 nXpos; 
    uint_16 nYpos; 
    TextBase* pNext; 
}; 

template <const size_t nSize> struct TextCache : public TextBase 
{ 
    char pBuffer[nSize]; 
}; 

Questo dovrebbe ridurre il getto richiesto.

3

C99 benedice la 'struct hack' - alias membro dell'array flessibile.

§6.7.2.1 Struttura e sindacali specificatori

¶16 Come caso particolare, l'ultimo elemento di una struttura con più di un membro denominato può avere un tipo array incompleto; questo è chiamato membro di array flessibile. Con due eccezioni , il membro di array flessibile viene ignorato. Innanzitutto, la dimensione della struttura deve essere uguale all'offset dell'ultimo elemento di una struttura altrimenti identica che sostituisce il membro di array flessibile con una matrice di lunghezza non specificata. 106) Secondo, quando a. (o ->) l'operatore ha un operando di sinistra che è (un puntatore a) una struttura con un membro di array flessibile e l'operando corretto nomina tale membro, si comporta come se quel membro fosse sostituito da con l'array più lungo (con il stesso tipo di elemento) che non renderebbe la struttura più grande dell'oggetto a cui si accede; l'offset dell'array deve rimanere quello del membro dell'array flessibile , anche se questo differirebbe da quello dell'array sostitutivo. Se questo array non ha elementi, si comporta come se avesse un elemento ma il comportamento è indefinito se viene effettuato un tentativo per accedere a quell'elemento o per generare un puntatore uno dopo lo .

¶17 ESEMPIO Supponendo che tutti i membri dell'array sono allineati stesso, dopo le dichiarazioni:

struct s { int n; double d[]; }; 
struct ss { int n; double d[1]; }; 

tre espressioni:

sizeof (struct s) 
offsetof(struct s, d) 
offsetof(struct ss, d) 

hanno lo stesso valore. La struttura della struttura ha un membro di array flessibile d.

106) La lunghezza è specificato per tener conto del fatto che le implementazioni possono dare diversi membri di matrice allineamenti secondo le loro lunghezze.

In caso contrario, utilizzare due allocazioni distinte: una per i dati di base nella struttura e la seconda per i dati aggiunti.

+0

Trova la citazione C++ Standard. L'OP sta chiaramente usando C++. – Puppy

+1

@DeadMG: Lo è, ma lo ha taggato [c] così le risposte c sono * in *; se l'OP non gli piace, dovrebbe aver pensato meglio in primo luogo. Non ci sono scuse per usare [c] [C++] a meno che tu non lo intenda. – dmckee

+0

le mie app, il tag C non dovrebbe essere stato lì, tuttavia, è bello sapere che il metodo che uso nei programmi C è 'in chiaro' (anche se in C non c'è davvero nessun altro modo per farlo) – Necrolis

0

Credo che, in C++, tecnicamente questo sia Comportamento non definito (a causa di problemi di allineamento), anche se sospetto che possa essere fatto funzionare probabilmente per ogni implementazione esistente.

Ma perché farlo comunque?

0

Si potrebbe utilizzare placement new in C++:

char *buff = new char[sizeof(TextCache) + (sizeof(char) * nLength)]; 
TextCache *pCache = new (buff) TextCache; 

L'unica avvertenza è che è necessario eliminare buff invece di pCache e se pCache ha un distruttore si dovrà chiamare manualmente.

Se avete intenzione di accedere a quest'area usando pBuffer mi consiglia di fare questo:

struct TextCache 
{ 
... 
    char *pBuffer; 
}; 
... 
char *buff = new char[sizeof(TextCache) + (sizeof(char) * nLength)]; 
TextCache *pCache = new (buff) TextCache; 
pCache->pBuffer = new (buff + sizeof(TextCache)) char[nLength]; 
... 
delete [] buff; 
0

Non c'è niente di sbagliato con la gestione propria memoria.

template<typename DerivedType, typename ElemType> struct appended_array { 
    ElemType* buffer; 
    int length; 
    ~appended_array() { 
     for(int i = 0; i < length; i++) 
      buffer->~ElemType(); 
     char* ptr = (char*)this - sizeof(DerivedType); 
     delete[] ptr; 
    } 
    static inline DerivedType* Create(int extra) { 
     char* newbuf = new char[sizeof(DerivedType) + (extra * sizeof(ElemType))]; 
     DerivedType* ptr = new (newbuf) DerivedType(); 
     ElemType* extrabuf = (ElemType*)newbuf[sizeof(DerivedType)];   
     for(int i = 0; i < extra; i++) 
      new (&extrabuf[i]) ElemType(); 
     ptr->lenghth = extra; 
     ptr->buffer = extrabuf; 
     return ptr;      
    } 
}; 

struct TextCache : appended_array<TextCache, char> 
{ 
    uint_32 fFlags; 
    uint_16 nXpos; 
    uint_16 nYpos; 
    TextCache* pNext; 
    // IT'S A MIRACLE! We have a buffer of size length and pointed to by buffer of type char that automagically appears for us in the Create function. 
}; 

Si dovrebbe considerare, tuttavia, che questa ottimizzazione è prematuro e non ci sono modo migliore modi di farlo, come avere un pool di oggetti o heap gestito. Inoltre, non ho considerato alcun allineamento, tuttavia è a mia conoscenza che sizeof() restituisce la dimensione allineata. Inoltre, questa sarà una cagna da mantenere per la costruzione non banale. Inoltre, questo è totalmente non testato. Un heap gestito è un'idea molto migliore. Ma non dovresti aver paura di gestire la tua memoria - se hai requisiti di memoria personalizzati, devi gestire la tua memoria.

Mi è appena venuto in mente che ho distrutto ma non cancellato la memoria "extra".

+0

In realtà ho un heap gestito, tuttavia, meno chiamate sono migliori, visto che ci sono migliaia di allocazioni/frees al secondo solo dal mio esempio precedente. Questo è stato effettivamente implementato perché ho avuto un enorme calo di fps (questo fa parte di un motore di caching del testo per il motore di rendering), con solo il testo disegnato e una piccola quantità di testo da avviare, tuttavia, dopo aver fatto questa ottimizzazione, è triplicato in fps, che ha risolto il collo di bottiglia che ha rallentato tutto fino a fermarsi quasi a – Necrolis

+0

Il punto di avere un heap gestito è che gli alloc sono virtualmente liberi e la memoria allocata su di esso è contigua. Penso che se un heap gestito non funziona per te (con questo specifico esempio, non sto dicendo che sono grandioso per tutti gli scopi), allora devi guardare di nuovo al tuo heap gestito. Gli assegnamenti sequenziali – Puppy

+0

non sono garantiti come contesti, almeno quando si utilizza un sistema di buddy binario, che è quello che sto usando, ma gli allocazioni sono (quasi) gratuiti. – Necrolis

2

Questo può sembrare eretico per quanto riguarda la mentalità economica dei programmatori C o C++, ma l'ultima volta che ho avuto un problema simile a risolvere ho scelto di inserire un buffer statico di dimensione fissa nella mia struct e accedervi tramite un puntatore indiretto. Se la struttura data è diventata più grande del mio buffer statico, il puntatore indiretto è stato quindi assegnato dinamicamente (e il buffer interno non utilizzato). È stato molto semplice implementare e risolvere il tipo di problemi sollevati come frammentazione, poiché il buffer statico è stato utilizzato in più del 95% del caso di utilizzo effettivo, mentre il restante 5% ha bisogno di buffer molto grandi, quindi non mi è importato molto di la piccola perdita di buffer interno.

+0

Questo principio di buffer statico piccolo + allocazione dell'heap quando richiesto viene spesso definito "Small String Optimization" per la classe di stringhe. Viene utilizzato (ad esempio) in 'std :: string' di Visual Studio 2010. Non so se c'è un nome più generico. –

Problemi correlati