2015-06-03 8 views
5

Mi chiedo quale sia il modo migliore per andare sull'inizializzazione e la memorizzazione degli oggetti per quanto riguarda gli oggetti che devono avere un ambito relativamente lungo/una lunga durata. Supponiamo di avere una classe GameEngine che deve essere inizializzata e contenere un riferimento a un Window per il rendering. Il riferimento è necessario per tutta la vita del programma e la finestra deve conoscere le sue dimensioni, almeno.Come gestire al meglio l'inizializzazione dell'oggetto C++: costruttori o puntatori vuoti?

In Java, lo farei in questo modo:

// Declaration: 
Window window; 
// Initialization: 
window = new Window(width, height); 

ho capito che in C++, il primo sarebbe già chiamare il costruttore di default della classe Window, quindi essere dichiarazione e inizializzazione . Avere un window = Window(width, height); sarebbe quindi assegnazione, buttando via l'oggetto già esistente.

La prima soluzione che ho trovato è stato quello di usare un puntatore:

// GameEngine.hpp 
class GameEngine { 
    Window *window; 
}; 

// Somewhere in GameEngine.cpp: 
window = new Window(width, height); 

Ma poi di nuovo, ho sempre letto uno dovrebbe favorire gli oggetti semplici oltre puntatori quando possibile e in effetti, mi sono fatto in un pasticcio di segnalatori in poco tempo, quindi sto cercando un altro modo.

Un'altra soluzione sembra per progettare gli oggetti ad avere un costruttore senza parametri e impostare l'oggetto in seguito:

// GameEngine.hpp 
class GameEngine { 
    Window window; 
}; 

// Somewhere in GameEngine.cpp 
window.setWidth(width); 
window.setHeight(height); 

questo funziona, ma ha un grave inconveniente: l'oggetto (almeno in questo caso) potrebbe essere in uno stato incoerente, poiché provare a visualizzare la finestra senza impostare larghezza/altezza causerebbe un errore o un arresto anomalo. Funziona per alcuni oggetti, ma per la maggior parte non lo fa.

Un modo per evitare questo sarebbe avere valori predefiniti. Ad esempio, il costruttore della classe Window potrebbe assomigliare a questo:

Window::Window(int width = 800, int height = 600) {} 

O anche come quella:

Window::Window() : width(DEFAULT_WIDTH), height(DEFAULT_HEIGHT) {} 

Ma in molti casi, i valori di default sarà difficile da determinare. Inoltre, da dove dovrebbero venire? La classe Window dovrebbe definire DEFAULT_WIDTH e DEFAULT_HEIGHT? O dovrei farlo anche io?

// GameEngine.hpp 
class GameEngine { 
    static const int DEFAULT_WIDTH = 800; 
    static const int DEFAULT_HEIGHT = 600; 
    Window window(800,600); 
}; 

Ma che sembra male, come ho letto che non si deve fare alcuna inizializzazione nell'intestazione, solo la dichiarazione, in modo che i valori di DEFAULT_WIDTH e DEFAULT_HEIGHT non dovrebbe in realtà essere conosciuto a questo punto (e solo essere inizializzato in .cpp, corretto?).

Mi manca un'opzione? Oppure è comune in C++ assumere che il programmatore dovrebbe sapere cosa sta facendo e occuparsi di ottenere i suoi oggetti in uno stato consistente prima di usarli? Quando utilizzare quale approccio?

+1

@close voto - questo non mi sembra una domanda basata sull'opinione pubblica. Anche se un po 'mal formulata (probabilmente a causa della mancanza di comprensione) la domanda riguarda davvero i tecnicismi del linguaggio C++. – Cubic

+0

I * think * si sta chiedendo come eseguire l'inizializzazione a due fasi –

+1

Mi sento come se mi mancasse qualcosa; c'è un motivo per cui non puoi creare l'oggetto Window e inizializzarlo allo stesso tempo? Hai bisogno che la dichiarazione e l'inizializzazione siano in posti diversi? Perché se non lo fai, includi semplicemente l'inizializzazione nella dichiarazione di dichiarazione e non ci sono problemi. – goldfire

risposta

4

Se si desidera costruirlo solo una volta e può essere eseguito nell'inizializzazione della classe, non è necessario un puntatore. È possibile dichiarare come membro e inizializzarlo nel costruttore in questo modo:

HPP

class Game 
{ 
    private: 
     Window window_; 

    public: 
     Game(int, int); 
} 

CPP

Game::Game(int width, int height) : window_(width, height) 
{ 
} 

Ciò costruire l'oggetto finestra quando si costruisce l'oggetto di gioco e persisterà fino a quando l'oggetto del gioco non viene distrutto. Se si vuole essere in grado di costruire in un secondo momento o ricostruire in qualsiasi momento quindi utilizzare uno std :: unique_ptr in questo modo:

HPP

class Game 
{ 
    private: 
     std::unique_ptr<Window> window_; 

    public: 
     Game(int, int); 
     void SomeMethod(int, int); 

} 

CPP

Game::Game(int width, int height) 
{ 
    window_ = std::make_unique<Window>(width, height); 
} 

Game::SomeMethod(int width, int height) 
{ 
    window_ = std::make_unique<Window>(width, height); 
} 

Questo cancellerà automaticamente la finestra quando l'oggetto Game viene distrutto e cancella automaticamente la finestra ogni volta che si chiama std :: make_unique per crearne uno nuovo. Ecco alcuni documenti su unique_ptr: http://en.cppreference.com/w/cpp/memory/unique_ptr

+0

Penso che questo mi abbia effettivamente reso conto del mio errore concettuale: ho pensato di avere la classe GameEngine {Window window; } 'chiamerebbe già il ctor di Window, perché' Window window; 'lo farebbe se si verificasse in una funzione del genere, giusto? Ma il fatto che non chiami il ctor qui e che possa inizializzare l'oggetto nell'elenco di inizializzazione dei membri di GameEngine è in effetti una soluzione soddisfacente per la maggior parte dei casi. Grazie per l'aiuto con la confusione di un principiante! – domsson

+1

Corretto, dichiarare qualcosa come membro di una classe non lo costruisce. Sarà costruito quando la classe è costruita. –

+0

Un'altra domanda; cosa succederebbe se non ** inizializzassi ** window' nel ctor di 'Game'? Sarebbe ancora non inizializzato o il compilatore chiamerebbe il ctor predefinito di 'Window'? – domsson

1

Sembra che tu abbia frainteso il C++. Non avresti mai lo Window window; così come in un'intestazione. Questo definisce un oggetto Window, ogni volta che viene inclusa l'intestazione!

Si può avere class GameEngine { Window window; .... } ma che in realtà non crare una finestra. Ogni costruttore di GameEngine ha un elenco di inizializzatori, e lì si inizializza window. Ha senso: il motore di gioco crea la finestra di cui ha bisogno.

+0

Scusa, ho ovviamente cercato di mantenere gli estratti del codice ** troppo ** brevi. Quella 'Finestra della finestra;' sarebbe all'interno della definizione della classe, ovviamente. Modificherò di conseguenza. Questo sarebbe quindi corretto, giusto? – domsson

1

Se si sta parlando di membri della classe, la dichiarazione è non lo stesso punto in cui viene chiamato il costruttore. L'inizializzazione di tali membri è esattamente ciò che gli elenchi di inizializzatori (di cui sembri sapere) sono per!

class Window { 
    int x; 
    int y; 
public: 
    Window(int x, int y); 
}; 

e

class Game { 
Window window; 
public: 
Game(); 
}; 

Quindi è possibile chiamare il costruttore della classe finestra dal costruttore di gioco in questo modo:

Game::Game() : window(DEFAULT_HEIGHT, DEFAULT_WIDTH) {} 

Nel caso in cui parlavi globali: Se hai davvero bisogno di un oggetto globale (anche se probabilmente non lo vuoi) puoi (e dovresti!) Dichiarare l'oggetto con external linkage nell'intestazione (che servirà solo a rendere il nome a disposizione, ma non chiamare qualsiasi costruttori) e fare la definizione per l'attuazione:

Dichiarazione:

extern Window window; 

Implementazione:

Window window(DEFAULT_WIDTH, DEFAULT_HEIGHT); 
+0

Grazie. La tua prima frase riassume in sostanza ciò che mi ha confuso; Pensavo che la dichiarazione dei membri della classe avrebbe innescato i loro costruttori. Se potessi, segnerei anche la tua risposta, ma ho dovuto sceglierne una. : \ – domsson

1

Idealmente , dovresti progettare le tue classi in modo che tutta l'inizializzazione di cui hai bisogno possa avvenire nel costruttore. Example here.

Ma questo non è sempre possibile (ad esempio se si desidera una finestra che non viene creata fino a quando non si verifica qualche evento particolare durante il gioco); o può essere difficile avvolgere la testa come un nuovo programmatore.


Un approccio consiste nell'utilizzare i puntatori, ma utilizzare un puntatore intelligente anziché un puntatore non elaborato.

Se la classe ha bisogno di contenere alcuni manici di oggetti ma non si è pronti per creare l'oggetto ancora, allora si può avere un membro della classe:

std::unique_ptr<Window> p_window; 

Poi, quando si è pronti per creare la finestra, è possibile eseguire il codice:

p_window.reset(new Window(bla bla bla)); 

il puntatore intelligente si prende cura di chiamare delete quando il suo oggetto contenente viene distrutto, e vi darà un errore di compilazione se si tenta accidentalmente di fare un "copia superficiale".

Per utilizzare il puntatore una volta puntato da qualche parte, scrivere p_window->bla... e per verificare se è stato ancora assegnato è possibile utilizzare if (p_window).

+0

Grazie anche a te. Penso di capire ora qual era il mio equivoco. Scusa per la confusione del mio principiante. Ancora una volta, grazie per l'input. :) – domsson

0

L'opzione che ti manca è inizializzare gli oggetti Window quando vengono creati. Non dichiarare oggetti Window nelle tue funzioni prima di sapere come inizializzarli. Se si dispone di un oggetto con membri Window, fare in modo che il costruttore sull'oggetto inizializzi il membro Window.

I puntatori vanno bene e la cosa giusta da fare se il tempo di creazione di un oggetto è davvero indeterminato, o è veramente e veramente necessario dichiarare una variabile per questo prima di essere pronti a creare un oggetto valido.

Il punto del consiglio si parla, non è quello di cambiare il modo di progettare gli oggetti, ma che è necessario riconsiderare come si uso gli oggetti: è necessario disimparare abitudini che hai raccolto da tutto-è-un -interpretazione di ambienti di programmazione come Java.

(anche se si dovrebbe usare puntatori intelligenti, come unique_ptr o shared_ptr a seconda dei casi)

(anche, se si crede una classe sarà quasi sempre bisogno di essere utilizzato con i puntatori, è utile fare una classe wrapper attorno al puntatore che agisce come un "oggetto semplice" anche se è implementato con un puntatore all'interno)

+0

Sì, penso che venire da Java può davvero portare ad una certa confusione quando si cerca di avvolgere il proprio cervello attorno al C++. Sto lottando più di quanto pensassi. Sta lentamente arrivando insieme però. Grazie per il tuo contributo. :) – domsson

Problemi correlati