Sto progettando una classe di tipo std::vector
per scopi di autoapprendimento, ma mi sono imbattuto in un problema di allocazioni di memoria all'interno dei costruttori.Allocazione di memoria all'interno dei costruttori?
Il costruttore di capacità di std::vector
è molto conveniente, ma ha il potenziale di lanciare un'eccezione std::bad_alloc
e abbattere l'intero programma con esso.
Sto lottando per decidere quale sarebbe il modo più elegante per gestire lo scenario improbabile del failover del costruttore di capacità o per informare meglio l'utente che utilizzando il costruttore, acconsentono che la struttura dati sia in grado di giù l'intero programma attraverso un'eccezione.
Il mio primo pensiero è stato quello di aggiungere un avviso in fase di compilazione ogni volta che viene chiamato il costruttore, ricordando che il costruttore può fallire e che dovrebbero assicurarsi di gestirlo o essere consapevoli dei rischi connessi con l'uso del costruttore .
Questa soluzione sembrava male perché, se applicata su scala globale, causerebbe troppi avvertimenti, e fare una cattiva impressione.
La mia seconda idea era rendere privato il costruttore e richiedere che il costruttore fosse raggiunto attraverso un metodo statico "requestConstruct", simile al modello Singleton.
Questa soluzione rende l'interfaccia strana.
Ho avuto qualche altra idea, ma tutte sembrano danneggiare la "sensazione" dell'interfaccia. Queste idee sono:
- spinta simile maybes
boost::optional
- Haskell-like
- costringere l'utente a darmi un pool di memoria che la struttura è autorizzata a. Questa idea sembra molto elegante ma non riesco a trovare un'implementazione di pool di memoria statica pulita/popolare per C/C++. Ho fatto comunque un test al https://github.com/dmitrymakhnin/MemoryPools.
- usa asserzioni descrittive che si scusano per aver fatto saltare in aria il mondo, e spiegano cosa è successo in dettaglio. Che è quello che sto facendo at the moment.
- provare ad allocare alcune altre volte prima di richiamare e arrestare l'intero programma. Questa sembra una buona strategia, ma sembra più come incrociare le dita, poiché il programma può ancora bloccarsi a causa del comportamento della struttura (piuttosto che del programma). Questo approccio potrebbe essere solo paranoico e sbagliato, dal momento che un'assegnazione fallita non ti darà necessariamente la possibilità di "riassegnare", e semplicemente spegnerà il programma.
- inventare una sorta di meccanismo di stress test per dare fiducia al proprietario della struttura che può gestire la maggior capacità che l'utente si aspetta (che può essere difficile perché questi stress-test possono essere molto fuorvianti, come la memoria può essere disponibile ora, ma in tempi più intensi di memoria, potrebbe non esserlo).
C'è anche una divertente possibilità di non avere abbastanza memoria per catturare effettivamente l'eccezione. Questo programma sembra non prenderlo (che potrebbe essere correlato ad avere abbastanza memoria).
#include <stdint.h>
#include <exception>
#include <iostream>
#include <stdlib.h>
int main(int argc, char **argv)
{
uint_fast32_t leaked_bytes = 0;
while (1) {
try {
new uint8_t;
} catch (const std::bad_alloc& e) {
std::cout << "successfully leaked" << leaked_bytes << " bytes." << '\n';
exit(0);
} catch (const std::exception& e) {
std::cout << "I caught an exception, but not sure what it was...\n";
exit(0);
}
++leaked_bytes;
}
}
Questo programma non mi permetta di gestire il fallimento prima che il programma è terminato però:
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
int main(int argc, char **argv)
{
uint_fast32_t bytes_leaked = 0;
while (1) {
if (malloc(1) == 0) {
printf("leaked %u bytes.\n", bytes_leaked);
exit(0);
}
++bytes_leaked;
}
return 0;
}
nothrow funziona correttamente così:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <new>
int main(int argc, char **argv)
{
uint64_t leaked_bytes = 0;
while (1) {
uint8_t *byte = new (std::nothrow) uint8_t;
if (byte == nullptr) {
printf("leaked %llu bytes.\n", leaked_bytes);
exit(0);
}
++leaked_bytes;
}
return 0;
}
EDIT:
penso Ho trovato una soluzione a questo problema. C'è un'innata ingenuità nella memorizzazione di strutture dati dinamiche sul processo principale. Cioè, queste strutture non sono garantite per avere successo e potrebbero rompersi in qualsiasi momento.
È preferibile eseguire la creazione di tutte le strutture dinamiche in un altro processo e disporre di una sorta di criterio di riavvio nel caso in cui abbia un problema imprevisto.
Questo impone un sovraccarico per la comunicazione alla struttura dati, ma impedisce al programma principale di dover gestire tutto ciò che può andare storto con la struttura dati, che può rapidamente assumere la maggioranza del codice in main.
fine EDIT.
Esiste un modo corretto per gestire i problemi di allocazione in tali classi dinamiche, aumentare la consapevolezza del mio utente in merito a tali rischi?
Per quanto ne so, std :: vector non ha un costruttore di capacità. Ha un costruttore di dimensioni che in realtà assegna gli oggetti non solo riserva una memoria –
Per me, lanciare un 'std :: bad_alloc' è il modo più idiomatico per segnalare un problema di" memoria insufficiente ". Dopotutto, il tuo costruttore non può realmente fare il suo lavoro se non riesce a recuperare la memoria. –
Ricorda che non è solo 'std :: vector' che può esaurire la memoria. Così può 'std :: string' o qualsiasi altra classe che usa la memoria. Ma la maggior parte dei sistemi a 64 bit non esaurisce la memoria. Invece, la memoria viene scambiata su disco e il programma si ferma a un arresto virtuale. – MSalters