Modo corretto per implementare un "array dinamico di oggetti non predefiniti-costruibili"

-4

Come molte altre domande e risposte già affermate, non vi è alcuna sintassi in C ++ che permetta di dichiarare e riempire una matrice di dimensioni dinamiche con oggetti costruttivi non predefiniti.

Obj* array = new Obj[size];

Qui, se Obj ha un costruttore predefinito, sarà usato per riempire array con size istanze predefinite di Obj , che è un problema.
Le risposte più ricorrenti a questa domanda menzionano i vettori ( qui ), o il meccanismo utilizzato dai vettori, il nuovo posizionamento ( qui ). Tuttavia, il primo non è un'opzione nel mio caso, e vorrei evitare quest'ultimo perché mi sembra un po 'sporca e disordinata (essere usato da STL forse lo rende un buon modo per fare le cose, ma in realtà sembra disordinato).
Modifica: perché i vettori non sono un'opzione: questo progetto è una sfida su cui voglio mettermi alla prova e voglio mettere le mani nel fango tanto quanto Io posso. Se non si utilizzano i vettori, si intende l'utilizzo delle notizie sul posizionamento come indicato di seguito, quindi è quello che farò.
Mi rendo conto che mi sono lamentato del fatto che le notizie sul posizionamento sono disordinate, e mi rendo conto anche che mettere le mani nel fango significa dover gestire un tale pasticcio.

Un'altra risposta ricorrente è usare le parentesi graffe per riempire l'array con oggetti non predefiniti:

Obj* array = new Obj[2] {Obj(foo), Obj(bar)};

Questo lo rende un array semi-dinamico , se posso dire. Essere allocati sull'heap non lo rende statico, ma la dimensione deve essere una costante in fase di compilazione, quindi non la considererei fully dinamica (o almeno non tanto quanto voglio che sia ).

La soluzione più ovvia per me sarebbe dichiarare l'array come sopra, lasciarlo riempire con oggetti junk e costruiti di default e quindi ri-riempirlo con gli oggetti corretti, come segue:

Obj* array = new Obj[size];
for (int i = 0; i < size; i++)
{
    array[i] = Obj(whatever);
}

Tuttavia, le prestazioni sono molto importanti nel mio programma e sono piuttosto preoccupato dell'impatto sulle prestazioni di tali metodi. Se size è 10000, l'array verrà riempito con 10000 oggetti indesiderati, che potrebbero essere molto costosi nel tempo. Quindi, il costo della sostituzione in seguito potrebbe essere anche peggiore (mentre è ancora possibile ridurre al minimo con un uso efficiente dell'idioma copy-and-swap ), e anche questa è una preoccupazione.
Invece, stavo pensando di usare un doppio malloc .

Obj** array = malloc(sizeof(Obj*) * size);
for (int i = 0; i < size; i++)
{
    array[i] = malloc(sizeof(Obj));
    array[i] = new Obj(whatever);
}
//...
for (int i = 0; i < size; i++)
{
    delete array[i];
    free(array[i]);
}
free(array);

Ma non sembra completamente cache-friendly. E non mi sembra giusto avere una coppia di malloc (avendo già uno solo non si sente bene).

Quindi ecco la mia domanda: c'è un modo bello e pulito che ti permette di allocare la memoria non inizializzata e poi riempirla con oggetti costruiti su misura, che non degradano le prestazioni?
Modifica: la mia vera domanda è: ci sono altri modi oltre a quelli che ho menzionato sopra?

( P.S.: So che la "memoria non inizializzata" non va bene con RAII, e quindi non va bene con "bello e pulito")

    
posta qreon 08.01.2017 - 08:14
fonte

2 risposte

2

Dato ciò che hai descritto nei commenti (volendo creare un array bidimensionale), esiste un'alternativa che evita sia l'uso esplicito del posizionamento nuovo per creare oggetti in memoria "raw" precedente, e l'uso terribile della cache di un array di modelli di array (implementato sia come array di puntatori per allocare separatamente i blocchi di memoria, sia come una sorta di cosa di std::vector<std::vector<T>> .

L'alternativa è allocare un blocco di memoria singolo per l'intera matrice rettangolare e fare un po 'di overloading dell'operatore per ottenere l'indirizzamento 2D in quel blocco.

Una semplice versione di questo aspetto è simile a questa:

class array2D { 
    std::vector<double> data;
    size_t columns;
public:
    array2D(size_t x, size_t y) : columns(x), data(x*y) {}

    double &operator(size_t x, size_t y) {
       return data[y*columns+x];
    }
};

Questo è "dinamico" solo nella misura in cui ti consente di specificare la dimensione dell'array quando lo crei, ma (così com'è ora) non puoi cambiare la dimensione dopo la creazione.

Se vuoi anche scrivere la tua imitazione di std::vector , è sempre possibile, ma è un po 'complicato sotto alcuni aspetti. Ho scritto un post sul blog alcuni anni fa che copre almeno alcuni dei punti, e @Loki Astari ha scritto una serie piuttosto ampia su di esso, completa con una Revisione del codice della sua implementazione.

    
risposta data 09.01.2017 - 06:25
fonte
1

is there a nice and clean way that allows you to allocate uninitialised memory, and then fill it with custom-constructed objects, that does not degrade performance?

Utilizza std::vector , reserve() e emplace_back() .

Perché reinventare la ruota se la libreria standard ha già risolto il problema!

However, the former [using vector] is not an option in my case,

Perché non è un'opzione?

Se qualche manager imposta alcune regole per proibire il vettore, prova a cambiare quella regola. Oppure trova un'implementazione open source di std :: vector e rinominala in not_std::not_a_vector .

Perché se risolvi questo problema correttamente, finirai per non aver reinventato solo std :: vector, ma avresti perso anche molto tempo.

    
risposta data 08.01.2017 - 11:47
fonte

Leggi altre domande sui tag