Passando a strutture readonly in C ++

4

In questo particolare problema che sto avendo, non sono sicuro del modo corretto di gestire le strutture readonly, passato ai costruttori di classi come parametro quando voglio memorizzare i dati nella struct, nella classe.

Ad esempio, ho una struttura che contiene alcune impostazioni

typedef struct s_MyStruct{
    int x;
    int y;
    std::string name;
    //... arbitrarily many properties.  
} MyStruct;

e ho una classe che prende un'istanza di MyStruct come parametro nel costruttore

class MyClass {
public:
    MyClass(MyStruct settings){
        //I would like to store the data in settings in the class
    }
}

La classe è istanziata in una funzione come questa:

void theFunction(){

    MyStruct m_settings { //.. initialization logic }

    MyClass *mClass = new MyClass(m_settings);

    //a reference mClass is passed somewhere so we can access it later
}

Quindi so quando "theFunction" lascia scope, assegnerà la variabile m_settings. Stavo pensando che potrei fare una copia della struttura, o forse memorizzare copie di tutte le variabili. O forse, invece, o inizializzando la struct nello stack, faccio solo un'allocazione dell'heap e pulisco la struct nel distruttore di classe. Non conosco il design corretto per questo tipo di messaggio che passa in C ++ Non sono abituato alla gestione manuale della memoria proveniente da uno sfondo di lingua gestita. Qual è il modo standard per gestire questa struttura ricca di impostazioni?

    
posta tt9 17.05.2016 - 22:29
fonte

2 risposte

3

Commento preliminare

In C ++ una struct è in effetti una classe con tutti i membri che sono pubblici. Quindi potresti definirlo più semplicemente:

struct MyStruct{
    int x;
    //... etc.  
};   // no need for typedef here !  

Passa il parametro con riferimento const al costruttore?

Detto questo, il modo in cui lo usi nel tuo costruttore fa in effetti una copia della tua struct (nel parametro). Se sei preoccupato delle dimensioni della struttura quando viene passato come argomento, puoi definire il costruttore come:

class MyClass {
public:
    MyClass(const MyStruct& settings) {  // pass a const reference
        ...
    }
};  // ending semi-colon please ;-) ! 

Vuoi conservare una copia locale dei parametri di inizializzazione?

Se vuoi mantenere una copia di tutti questi parametri nel tuo oggetto costruito, usa solo un inizializzatore di mem:

class MyClass {
    MyStruct m_settings;   // local copy of parameters
public:
    MyClass(const MyStruct& settings) : m_settings(settings) {  
        ...
    }
};

Non correre rischi inutili con l'allocazione manuale della memoria

Ovviamente, puoi fare come pensavi: allocare un oggetto MyStruct nell'archivio gratuito e passare un puntatore. Funziona tecnicamente, ma questo non è il modo di andare in C ++. Rischia di perdere memoria in caso di errori imprevisti o se non te ne occupi nel distruttore. Rischiate copie poco profonde quando copiate la struttura se non fornite un costruttore di copie e un operatore di assegnazione. Quindi dovresti occuparti della regola del 3 mentre nell'approccio I ti abbiamo proposto che puoi trarre vantaggi dal costruttore di copie, dall'assegnazione di copia e dal distruttore di default.

Se vuoi comunque andare in questo modo e occuparti di tutto ciò, considera almeno l'uso di un shared_ptr<MyStruct> invece di un grezzo MyStruct* puntatore.

    
risposta data 17.05.2016 - 23:16
fonte
3

In questo caso, la struttura delle impostazioni e l'oggetto che si desidera costruire con tali impostazioni condividono la stessa durata: la durata di una chiamata a theFunction . Quindi l'opzione migliore è probabilmente la più semplice: non preoccupatevi nemmeno di dare loro variabili separate in primo luogo. Nel moderno C ++, potrebbe essere conciso come:

MyClass mClass({ /* struct arguments */ });

Non solo è più conciso, ma è potenzialmente più efficiente. Poiché la struct è un temporaneo senza nome, vive solo per la durata di questa chiamata del costruttore, il che significa che almeno in teoria può tranquillamente essere spostato nel costruttore mClass anziché copiato.

Inoltre, dovresti fare in modo che il costruttore MyClass prenda le impostazioni struct per riferimento (se possibile, const riferimento). Fare passare per valore di solito costringe una copia, che sembra completamente inutile in questo caso.

In generale, potrebbe non essere così semplice, e le varie domande che aggiungi alla fine implicano un consiglio leggermente più generale.

I don't know the proper design for this sort of message passing in C++ I am not used to manual memory management coming from a managed language background. What is the standard way to handle this struct full of settings?

La cosa importante da capire è che nel C ++ moderno, non dovresti quasi mai gestire la memoria manualmente . In particolare, quasi sempre si vogliono usare variabili allocate nello stack o puntatori intelligenti alle variabili allocate su heap invece dei puntatori "grezzi" e si preferisce quasi sempre lavorare con le classi che implementano RAII (cioè hanno distruttori che puliscono correttamente su di sé) invece di affidarsi alle coppie "nude" nuove / eliminate.

In particolare, se non hai bisogno di mClass per essere un puntatore (sia raw che smart), non rendine uno.

MyClass mClass; // doesn't get any simpler than this

Se hai davvero bisogno che sia un puntatore, usa un puntatore intelligente per assicurarti che venga pulito correttamente, qualunque cosa accada.

// the 'auto' is deduced to be 'std::unique_ptr<MyClass>'
auto mClass = std::make_unique<MyClass>(MyStruct{ /* struct arguments */ });

Il tuo puntatore intelligente dovrebbe essere std::unique_ptr poiché (di solito) ha un sovraccarico di runtime pari a zero rispetto a un puntatore non elaborato, applica una proprietà univoca non consentendo di copiarlo (che di solito è ciò che desideri), e può essere facilmente convertito in altri puntatori intelligenti come std::shared_ptr se hai bisogno di un comportamento più complicato.

Entrambe queste opzioni garantiscono che l'oggetto mClass venga distrutto e deallocato anche in presenza di eccezioni o di ritorni anticipati. Non devi mai scrivere un esplicito "per favore distruggi questo ora" in qualsiasi parte del tuo codice.

Consiglio vivamente di leggere Effective Modern C ++ per ulteriori dettagli su questi argomenti.

Or maybe instead or initializing the struct on the stack I just do a heap allocation and clean up the struct in the class destructor.

Questo è del tutto possibile, ma eccessivamente complicato e un po 'inutile.

    
risposta data 17.05.2016 - 22:58
fonte

Leggi altre domande sui tag