2013-08-20 8 views
9

Mi piacerebbe mantenere MyClass nella memoria dello stack (più semplice, più veloce) nel codice seguente, ma evitare di chiamare il costruttore di default:allocare memoria sullo stack senza chiamare il costruttore

#include <iostream> 

class MyClass { 
public: 
    MyClass() { 
    std::cout << "MyClass()" << std::endl; 
    } 

    MyClass(int a) { 
    std::cout << "MyClass(" << a << ")" << std::endl; 
    } 

    MyClass(const std::string& a) { 
    std::cout << "MyClass(\"" << a << "\")" << std::endl; 
    } 

    void doStuff() { 
    std::cout << "doStuff()" << std::endl; 
    } 
}; 

int main(int argc, char* argv[]) { 
    bool something; 
    if (argc > 1) 
    something = 1; 
    else 
    something = 0; 

    MyClass c; 
    if (something) 
    c = MyClass(1); 
    else 
    c = MyClass("string"); 
    c.doStuff(); 

    return 0; 
} 

Per quanto ne so , l'unico modo per evitare di chiamare il costruttore predefinito sarebbe quello di usare un puntatore, ma poi dovrei allocare nell'heap e gestire la gestione della memoria. C'è un altro modo?

+1

forse si potrebbe sottoclasse e sovrascrivere il costruttore di default e l'abbiano in essere un no-op? – nielsbot

+0

In questo caso particolare, l'operatore ternario può essere utile. –

+1

@Benjamin: l'operatore ternario non funzionerà perché i risultati sono di due tipi diversi. – Cameron

risposta

11

Se non ti dispiace usare un trucco totale, puoi provare un posizionamento new.

char mem[sizeof(MyClass)] alignas(MyClass); 
auto custom_deleter = [](MyClass *p){ p->~MyClass(); }; 
std::shared_ptr<MyClass> c(new (mem) MyClass, custom_deleter); 

si utilizza il alignas per assicurarsi che la memoria allocata automatica sia correttamente allineata per l'oggetto. La funzione custom_deleter chiama il distruttore senza liberare la memoria, necessaria quando si utilizza la memoria automatica con un puntatore intelligente. Una demo del codice può essere trovata here.

Ma, per il tuo problema esiste una soluzione più elegante. È possibile utilizzare un costruttore di copia, invece:

MyClass c = something ? MyClass(1) : MyClass("string"); 
    c.doStuff(); 
5

Hai ragione. Non è possibile evitare di chiamare il costruttore predefinito, a meno che non si desideri duplicare un codice come mostrato di seguito.

if (something) { 
    MyClass c(1); 
    c.doStuff(); 
} 
else { 
    MyClass c("string"); 
    c.doStuff(); 
} 

Si consiglia di creare l'oggetto dell'heap, ma delegare la gestione della memoria a un'altra classe. Con C++ 03, esiste la classe std::auto_ptr che può essere utilizzata. Con C++ 11, auto_ptr è deprecato e invece è possibile utilizzare shared_ptr o unique_ptr.

Ecco qualche esempio di codice utilizzando shared_ptr -

std::shared_ptr<MyClass> c; 
if (something) 
    c.reset(new MyClass(1)); 
else 
    c.reset(new MyClass("string")); 

c->doStuff(); 

L'oggetto sarà automaticamente cancellato quando va fuori di portata.

In generale, si consiglia di utilizzare puntatori intelligenti anziché eseguire autonomamente la gestione della memoria. Questo è particolarmente utile quando hai a che fare con codice che può generare eccezioni.

5

suggerimento di Benjamin Bannier lavora per me su GCC 4.7.2 senza flag di compilazione speciale (vale a dire, di ottimizzazione di default), oppure con -O0, -O1, -O2, o -O3:

int main(int argc, char* argv[]) { 
    bool something; 
    if (argc > 1) 
    something = 1; 
    else 
    something = 0; 

    MyClass c = something ? MyClass(1) : MyClass("string"); 

    c.doStuff(); 

    return 0; 
} 

ottengo gli stessi risultati quando provo su GCC 3.4.4 (circa 2004), GCC 3.4.6 (2006), GCC 4.2.4 (2007), e GCC 4.7.2 (2012).

+0

Inoltre sembra essere un po 'più prolisso del necessario. :) 'MyClass c = (argc> 1)? MyClass (1): MyClass ("stringa"); ' – cHao

+0

@cHao - Stavo semplicemente inserendo il codice nel framework esistente dell'OP. – phonetagger

2

prova, questo potrebbe evitare di copia in più a causa di compilatore copia elisione:

MyClass makeInstance(int a, string& b) { 
    if (something) { 
     return MyClass(a); 
    } else { 
     return MyClass(b); 
    } 
} 

ho provato e nel mio caso vedo un solo oggetto costruito e distrutto.

+0

'something' non è nel campo di applicazione qui. Inoltre, questo mi costringe a creare sempre sia un 'int' che una' stringa', anche se voglio solo una forma. Questo potrebbe essere risolto con una funzione di sovraccarico, ma poi si entra chiaramente nella terra di ipersponoscimento quando sono disponibili soluzioni molto più semplici. –

0

C'è solo una buona soluzione per evitare chiamate di costruzione non necessarie: inizializzazione pigra. Avere un solo costruttore, che porta semplicemente il tuo oggetto in uno stato definito, e fa l'effettiva inizializzazione in un metodo init().Come questo:

class MyClass { 
public: 
    MyClass() : initialized(false) { std::cout << "MyClass()" << std::endl; }; 

    void init(int a) { 
     std::cout << "MyClass(" << a << ")" << std::endl; 
     initialized = true; 
    }; 

    void init(const std::string& a) { 
     std::cout << "MyClass(\"" << a << "\")" << std::endl; 
     initialized = true; 
    }; 

    void doStuff() { 
     assert(initialized); 
     std::cout << "doStuff()" << std::endl; 
    }; 

private: 
    bool initialized; 
}; 

allora si può facilmente effettuare le seguenti operazioni, l'inizializzazione obiettate esattamente una volta senza l'utilizzo di alcun tipo di hack:

MyClass c; 
    if (something) { 
     c.init(1); 
    } else { 
     c.init("string"); 
    } 
    c.doStuff(); 
+0

Il richiedente voleva evitare di chiamare il costruttore predefinito (forse in modo irragionevole, ma quello era il requisito). Ma qualcosa di simile a quello che hai proposto può essere adattato in una soluzione di lavoro seguendo il [modello costruttore] (http://en.wikipedia.org/wiki/Builder_pattern). Quindi, l'inizializzazione (cioè la creazione) di 'c' può essere rimandata fino a quando il builder non viene inizializzato correttamente. Quindi 'c' è inizializzato dal costruttore. – jxh

+0

@jxh È vero che l'OP ha chiesto come evitare il costruttore predefinito, ma da quello che posso vedere, farlo non è un suo problema; il suo problema sembra essere un'inizializzazione non necessaria. E questo è il problema che ho cercato di risolvere. – cmaster

Problemi correlati