2010-04-30 27 views
7

Attualmente sto lavorando a un progetto C++, dove appaiono spesso gli array dinamici. Mi chiedevo, quale potrebbe essere il modo corretto per inizializzare un array dinamico usando il nuovo operatore? Un mio collega mi ha detto che è un no-no usare new all'interno del costruttore, dal momento che un costruttore è un costrutto che non dovrebbe essere incline agli errori o non dovrebbe fallire del tutto, rispettivamente. Consideriamo ora il seguente esempio: Abbiamo due classi, uno stato di classe più o meno complesso e uno StateContainer di classe, che dovrebbe essere auto-spiegato.Modo corretto per inizializzare la matrice dinamica in C++

class State { 
private: 
unsigned smth; 
public: 
State(); 
State(unsigned s); 
}; 

class StateContainer { 
private: 
unsigned long nStates; 
State *states; 
public: 
StateContainer(); 
StateContainer(unsigned long n); 
virtual ~StateContainer(); 
}; 

StateContainer::StateContainer() { 
nStates = SOME_DEFINE_N_STATES; 
states = new State[nStates]; 
if (!states) { 
    // Error handling 
} 
} 

StateContainer::StateContainer(unsigned long n) { 
nStates = n; 
try { 
    states = new State[nStates] 
} catch (std::bad_alloc &e) { 
    // Error handling 
} 
} 

StateContainer::~StateContainer() { 
if (states) { 
    delete[] states; 
    states = 0; 
} 
} 

Ora in realtà, ho due domande:

1.) è ok, a indire nuove all'interno di un costruttore, o è meglio creare un init supplementare() - Metodo per la Stato- Matrice e perché?

2.) Che cosa è il modo migliore per verificare se il nuovo riuscito:

if (!ptr) std::cerr << "new failed." 

o

try { /*new*/ } catch (std::bad_alloc) { /*handling*/ } 

3.) Ok le sue tre questioni; o) sotto il cofano, nuova fa una sorta di

ptr = (Struct *)malloc(N*sizeof(Struct)); 

E quindi chiamare il costruttore, giusto?

+8

Suppongo che tu non sia autorizzato a usare 'vector'? – GManNickG

+0

Il tuo collega è sbagliato. È perfettamente accettabile usare 'new' in un costruttore. –

risposta

7

Si dovrebbe propagare lo std::bad_alloc - probabilmente non è ragionevole fare nulla.

Prima di tutto, lanciare un'eccezione dal costruttore is the only reliable way to signal a problem - se non c'è eccezione, significa che l'oggetto è completamente costruito. Quindi prendere lo std::bad_alloc da solo non aiuta contro altre possibili eccezioni.

Quindi cosa si può fare per "gestirlo" in modo che l'altro codice sia a conoscenza e possa reagire in modo appropriato?

Utilizzare le eccezioni a destra - let them propagate to the site where they can be reasonably handled.

0

Il tuo amico ha ragione. Tuttavia, è prassi comune effettuare prenotazioni di memoria in Constructor e deallocations nel Destructor.

Il problema con i metodi che potrebbero non riuscire in Costruttori è il seguente: Il costruttore non ha mezzi tradizionali per notificare il chiamante del problema. Il chiamante si aspetta di ottenere un'istanza dell'oggetto che non sia corrotta in alcun modo.

La soluzione a questo problema è generare/propagare un'eccezione. L'eccezione passerà sempre e può essere gestita dal chiamante.

+0

La tua risposta è contraddittoria; dici che non esiste un modo tradizionale di notifica all'utente di un errore, quindi procedi descrivendone uno. –

+1

Il suo amico ha torto, con la maiuscola W. Fine della storia. – jalf

+0

Le eccezioni sono così impopolari, la maggior parte del C++ le usa solo quando è necessario. Nel loro pensiero "tradizionale" non c'è posto per le eccezioni ed è per questo che non le vedono come un modo per avvisare l'utente. – ypnos

1

Non una risposta completa, solo i miei 2 centesimi:

1: userei nuovo nel costruttore, anche se per gli array dinamici, STL è la strada da percorrere.

2: la normale gestione degli errori per nuovi è di generare un'eccezione, quindi è inutile controllare il puntatore restituito.

3: non dimenticare il nuovo operatore per rendere la storia un po 'più interessante.

5

L'intero scopo di un costruttore è di costruire un oggetto. Ciò include l'inizializzazione. Quando il costruttore termina l'esecuzione, l'oggetto dovrebbe essere pronto per l'uso. Ciò significa che il costruttore deve eseguire qualsiasi inizializzazione necessaria.

Ciò che il tuo amico suggerisce porta a codice non attuabile e soggetto a errori, e va contro ogni buona pratica del C++. Lui ha torto.

Come per gli array dinamici, utilizzare invece std::vector. E per inizializzare, è sufficiente passare un parametro al costruttore:

std::vector<int>(10, 20) 

creerà un vettore di 10 interi, tutti loro inizializzato al valore 20.

+0

Cosa intendi con "include"? Per me, * costruzione * e * inizializzazione * sono sinonimi. – fredoverflow

0
  1. Se siete alla ricerca fuori per un tipo di ritorno, cioè se la funzione deve restituire uno stato, utilizzare la funzione separata (init()) per allocare la memoria.

    Se si controlla se la memoria viene allocata controllando la condizione NULL in tutte le funzioni membro, quindi allocare memoria nel costruttore stesso.

  2. La gestione delle eccezioni (ad esempio, prova ... cattura) è una scelta migliore.

  3. Oltre a chiamare il costruttore, anche il puntatore "this" viene inizializzato.

1

Risposta breve:
No, il tuo amico è sbagliato. Il costruttore è dove esegui allocazione + inizializzazione. Abbiamo persino un termine chiamato "Acquisizione delle risorse è inizializzazione" (RAII) ... le classi acquisiscono risorse come parte della loro inizializzazione nel costruttore e le classi liberano quelle risorse acquisite nei loro distruttori.

Risposta lunga:

Si consideri il seguente codice in un costruttore:

member1 = new whatever1[n]; 
member2 = new whatever2[m]; 

Ora, supponiamo che in quanto sopra che la seconda assegnazione dovesse un'eccezione, sia perché la costruzione di qualunque cosa2 ha fallito e ha generato un'eccezione, o l'allocazione fallita e ha gettato std :: bad_alloc. Il risultato è che la memoria che hai assegnato per qualsiasi cosa1 sarà trapelata.

Per questo motivo, è preferibile utilizzare una classe contenitore come std :: vector:

MyClass::MyClass(std::size_t n, std::size_t m) : member1(n), member2(m) {} 
// where member1 and member2 are of type std::vector 

Quando si utilizza il tipo std :: vector, se il secondo allocazione fallisce, il distruttore std precedente :: verrà invocato il vettore, in modo che le risorse vengano rilasciate in modo appropriato. Questo è probabilmente ciò che intendeva il tuo amico quando ha detto che non dovresti usare new (dovresti usare una classe contenitore, invece), nel qual caso sarebbe per lo più corretto ... anche se ci sono classi di puntatori intelligenti come boost :: shared_ptr che ti fornisce le stesse garanzie di sicurezza e dove dovresti ancora usare new, quindi non ha ancora ragione.

Si noti che se si dispone solo di un oggetto/array che si sta allocando, non si tratta di un problema ... che è il modo in cui le cose sono nel codice ... non è necessario preoccuparsi di perdite dovute a qualche altra allocazione in mancanza. Inoltre, dovrei aggiungere che nuovo avrà esito positivo o genererà un'eccezione; non restituirà NULL e quindi il controllo non ha senso. L'unica istanza in cui dovresti prendere std :: bad_alloc è nel caso in cui sei stato costretto a fare diverse assegnazioni (ti è vietato usare std :: vector), nel qual caso hai deallocato le altre risorse nel gestore, ma poi riformuli l'eccezione, perché dovresti lasciarla propagare.

Problemi correlati