2013-04-05 14 views
24

Sto sempre leggendo che non dovrei lanciare un std::string o altre classi che assegnano memoria. come here o, più importante, here al punto 3. - Non incorporare un oggetto std::string.(non) utilizzando std :: string in eccezioni

Così ora sto cercando di inserire boost::exception al mio progetto e cosa vedo: lots of strings.

Perché la spinta non è conforme alle proprie raccomandazioni?

E se ho i parametri che non possono essere codificato, come safed in un file di configurazione, come posso mettere in un'eccezione, senza l'utilizzo di std::string?

O è la linea guida non utilizzare std::string solo fanno uso std::string il meno possibile linea guida? Sono un po 'confuso ...

Ho fatto qualche ricerca. Per favore correggimi se sbaglio.


Se ho capito bene, è tutta una questione l'assegnazione durante il tiro e ciò che sta accadendo alla memoria allocata. Quindi la memoria si perde se la alloco nel costruttore e non può essere liberata nel distruttore dell'eccezione, che produrrà una perdita di memoria. Ma va bene allocarlo prima di lanciare, quindi l'eccezione è pulita.

ho provato questo:

struct xexception { 
    int *ttt[10]; 
    xexception() { 
    ttt[0] = new int[0xfffffffL]; 
    ttt[1] = new int[0xfffffffL]; 
    ttt[2] = new int[0xfffffffL]; 
    ttt[3] = new int[0xfffffffL]; 
    ttt[4] = new int[0xfffffffL]; 
    ttt[5] = new int[0xfffffffL]; 
    ttt[6] = new int[0xfffffffL]; 
    ttt[7] = new int[0xfffffffL]; 
    ttt[8] = new int[0xfffffffL]; 
    ttt[9] = new int[0xfffffffL]; 
    } 

    ~xexception() throw() { 
    //never happen 
    delete[] ttt[0]; 
    delete[] ttt[1]; 
    delete[] ttt[2]; 
    delete[] ttt[3]; 
    delete[] ttt[4]; 
    delete[] ttt[5]; 
    delete[] ttt[6]; 
    delete[] ttt[7]; 
    delete[] ttt[8]; 
    delete[] ttt[9]; 
    } 
}; 

int main(int argc, const char *argv[]) { 
    try { 
    throw(xexception()); 
    } 
    catch (const xexception &e) { 
    std::cerr << "\nttt " << e.ttt[0][0] << std::endl; 
    } 
    catch (std::bad_alloc) { 
    std::cerr << "bad alloc" << std::endl; 
    } 

    return 0; 
} 

Il risultato è, ho la bad_alloc e un enorme perdita di memoria.

Ora se eseguo l'allocazione in precedenza, è anche lancia il bad_alloc ma prima che venga creata l'eccezione.


mio eccezione al concetto eccezione è:

Chi se ne frega? Se ho un bad_alloc nel mio programma, a causa di una memory_leak o qualcos'altro (sto parlando di programmi su PC e non di microcontrollori) ho altri problemi. Forse riesco a capire che è successo un male_alloc, ma dove? Sulla mia allocazione durante una funzione (una delle forse 1000) o nello std::string (beh so che è la stringa ma ... nessuna possibilità di manipolare la memoria della stringa ... o la sua da dissipare).

try { 
    // where is the error??? 
    int *x = new int[100]; // here? 
    .... 
    int *y = new int[100]; // or here? 
    .... 
    int *z = new int[100]; 
    .... 
    int *w = new int[100]; 
    .... 
    int *t = new int[100]; 
    .... 
    int *f = new int[100]; 

    .... 

    std::string str("asdfasdfasdfasdfasdfasdfasdf"); // maybe here 
} 
catch (the error) { 
    .... 
} 

E poi? Devo cercare di capire dove sta succedendo? Quindi userei valgrind non eccezioni.

void foo() { 
    int *i = new int[1]; 
    foo(); 
} 

try { 
    foo(); 
} 
chatch(bad_boy) { 
    go_exception_handler_go(parameters); // oh, shit happens: also an stack_overflow may happend, cause stack is also full 
} 

O devo manipolare l'errormessage e log, che cosa definitivamente getterebbe la prossima bad_alloc.

Per favore non fraintendermi.Da quando ho visto il boost :: exception ho riscritto la mia classe di eccezione (fino ad aspettare una risposta) ma penso anche che non sia davvero necessario raccogliere ogni granello di sabbia.

+3

Non vedo alcun 'std :: string's che sia * membri * di' eccezione'. –

+1

'std :: runtime_error' usa un' std :: string'. –

+0

quindi usare una stringa come parametro è ok? – user1810087

risposta

17

Il consiglio indica fondamentalmente "Non utilizzare alcun costrutto che potrebbe generare un'eccezione in un'eccezione". Questo perché se ottieni un'eccezione durante il tentativo di generare un'eccezione, il runtime di C++ chiama immediatamente terminate() e uccide il tuo programma.

Ora, se (o) le eccezioni coinvolte chiamerebbero semplicemente terminate() (come predefinito per un'eccezione non rilevata), non è necessario preoccuparsene. Ad esempio, se l'applicazione non è in grado di gestire bad_alloc (impossibile ripristinare dalla memoria esaurita), non è necessario preoccuparsi dei costruttori di copia (come std::string) che potrebbero lanciarlo.

Ma se si desidera essere in grado di rilevare e ripristinare da un bad_alloc, è necessario assicurarsi che nessuno dei costruttori di copie di eccezione possa causarne uno. Se stai scrivendo una libreria che verranno utilizzate da altre applicazioni, non dovresti assumere che l'applicazione non desideri gestire bad_alloc.

C++ 11 rendono questo molto più semplice utilizzando i costruttori di movimento (anziché i costruttori di copie) laddove possibile. Poiché il costruttore di spostamenti per std::string non genera mai eccezioni, è possibile utilizzare in modo sicuro un std:string nel tipo di eccezione purché si implementino correttamente i costruttori di movimento e si assicuri che vengano utilizzati. Si noti che la costruzione iniziale dell'oggetto da lanciare in un'espressione di lancio NON fa parte del processo di lancio delle eccezioni, quindi il costruttore può lanciare un'eccezione senza causare una doppia eccezione (e terminare()). Quindi, se avete:

throw some_function(); 

some_function potrebbe generare un'eccezione (come bad_alloc) senza ritornare un oggetto da buttare e va bene. Se non genera un'eccezione (e restituisce un oggetto valido), verrà utilizzato il costruttore di movimento per il tipo di eccezione (se disponibile) per il processo di lancio delle eccezioni e tale costruttore di movimento non deve generare un'eccezione.


Completamente indipendente di quanto sopra, ogni volta che si chiama new è necessario assicurarsi che esattamente un punto chiamerà delete in tutti i casi possibili, o ti perdere la memoria (o crash da una doppia eliminazione). Ciò diventa complicato ogni volta che si ha una funzione che chiama new e quindi fa qualcos'altro che potrebbe generare un'eccezione (ad esempio, chiamare di nuovo new). Se ciò accade in un costruttore, il distruttore per l'oggetto non verrà chiamato (sebbene i distruttori per le classi di base e i campi saranno), quindi non è possibile eseguire la disinfezione nel distruttore come si tenta di fare con il proprio esempio.

Fortunatamente std::unique_ptr esiste per rendere tutto ciò molto più semplice. Se scrivi la tua classe di eccezione come:

struct xexception { 
    std::unique_ptr<int[]> ttt[10]; 
    xexception() { 
    ttt[0].reset(new int[0xfffffffL]); 
    ttt[1].reset(new int[0xfffffffL]); 
    ttt[2].reset(new int[0xfffffffL]); 
    ttt[3].reset(new int[0xfffffffL]); 
    ttt[4].reset(new int[0xfffffffL]); 
    ttt[5].reset(new int[0xfffffffL]); 
    ttt[6].reset(new int[0xfffffffL]); 
    ttt[7].reset(new int[0xfffffffL]); 
    ttt[8].reset(new int[0xfffffffL]); 
    ttt[9].reset(new int[0xfffffffL]); 
    } 
}; 

dovrebbe funzionare e non perdere memoria.

+0

"puoi usare tranquillamente una stringa: stringa nel tuo tipo di eccezione" Che ne dici di costruire (comporre) l'iniziale 'std :: string'? Ciò potrebbe anche comportare un'eccezione (è probabile che si crei un 'bad_alloc' come qualsiasi operazione di copia della stringa IMHO). – dyp

+3

@DyP: la creazione dell'oggetto iniziale che verrà lanciato non fa parte dell'effettiva eccezione di lancio, quindi se causa un 'bad_alloc' non causerà una doppia eccezione immeditate' terminate() ' –

+0

True, ma penso che sia un motivo per non usare 'std :: string' come membri di dati non statici di eccezione (non ref, non-ptr) (perché deve essere creato/composto). – dyp

3

Anche se penso che non si usi std::string per core, le eccezioni fondamentali potrebbero essere una buona linea guida, non penso che le librerie/applicazioni rivolte all'utente debbano necessariamente seguire questo.

Ci possono essere altri motivi, ma si preme su quello primario: si desidera indicare all'utente (o allo sviluppatore) informazioni contestualmente significative, che spesso non si possono fare con una semplice stringa letterale. Per fare ciò è necessario disporre di un'allocazione dinamica. Se, per qualche motivo, avevi uno bad_alloc, probabilmente sei già pronto per iniziare, quindi non ti compra/non ti perda nulla.

Edit:

A proposito: il distruttore di std::exception è contrassegnato come virtuale per una ragione!

+0

Perché queste informazioni contestualmente significative devono essere memorizzate in una stringa? Perché non utilizzare tipi di eccezioni derivati ​​specificamente con membri di dati specifici (potrebbe essere necessario utilizzare le stringhe _here_)? La composizione della stringa durante la creazione dell'eccezione è inutile se nessuno la usa, quindi perché non crearla su richiesta? – dyp

+1

Non sto dicendo che è necessario; Sto dicendo che potrebbe essere utile. Inoltre, tutte le eccezioni saranno allocate dinamicamente in modo efficace, comunque. (Pensa, come funziona 'std :: exception const &' diventa 'MyException const &'?). Le allocazioni dinamiche per le eccezioni dovrebbero essere evitate, ma non direi che è un no-no. Il punto era, se non hai abbastanza memoria per allocare una stringa per fornire un errore significativo, probabilmente hai problemi altrove. –

+0

"Inoltre, tutte le eccezioni saranno effettivamente allocate dinamicamente, comunque." vale a dire - altrimenti il ​​runtime deve conoscere staticamente al momento del collegamento la dimensione di tutte le possibili eccezioni che possono verificarsi. E ci sono certamente circostanze in cui potrebbe non sapere. –

Problemi correlati