15

Ho due classi di gestione risorse DeviceContext e OpenGLContext entrambi membri di class DisplayOpenGL. La durata della risorsa è legata a DisplayOpenGL. L'inizializzazione assomiglia a questo (pseudo codice):C++ - Esegue una funzione prima di inizializzare un membro della classe

DeviceContext m_device = DeviceContext(hwnd); 
m_device.SetPixelFormat(); 
OpenGLContext m_opengl = OpenGLContext(m_device); 

Il problema è la chiamata a SetPixelFormat(), dal momento che non posso farlo nella lista di inizializzazione del DisplayOpenGL c'tor:

class DisplayOpenGL { 
public: 
    DisplayOpenGL(HWND hwnd) 
    : m_device(hwnd), 
     // <- Must call m_device.SetPixelFormat here -> 
     m_opengl(m_device) { }; 
private: 
    DeviceContext m_device; 
    OpenGLContext m_opengl; 
}; 

Soluzioni che posso vedere:

  • Inserimento m_dummy(m_device.SetPixelFormat()) - non funzionerà come SetPixelFormat() non ha retval. (si dovrebbe fare questo se avesse un retval?)
  • Utilizzare unique_ptr<OpenGLContext> m_opengl; anziché OpenGLContext m_opengl;.
    Poi inizializzare come m_opengl(), chiamare SetPixelFormat() nel corpo c'tor e utilizzare m_opengl.reset(new OpenGLContext);
  • chiamata SetPixelFormat() da DeviceContext c'tor

Quale di queste soluzioni è preferibile e perché? Qualcosa che mi manca?

Sto usando Visual Studio 2010 Express su Windows, se è importante.

Modifica: Sono principalmente interessato ai compromessi coinvolti nel decidere per uno di questi metodi.

  • m_dummy() non funziona e sembra poco elegante, anche se sarebbe
  • unique_ptr<X> è interessante per me - quando sarebbe lo uso al posto di un "normale" X m_x membro? I due metodi sembrano essere funzionalmente più o meno equivalenti, ad eccezione dei problemi di inizializzazione.
  • Chiamare SetPixelFormat() da DeviceContext c'tor funziona sicuramente ma non mi sembra impuro. DeviceContext dovrebbe gestire la risorsa e abilitarne l'uso, non imporre alcun criterio di formato pixel casuale agli utenti.
  • stijn'sInitDev() sembra la soluzione più pulita.

Preferisco quasi sempre una soluzione basata su puntatore intelligente in questi casi?

+0

Questo mi sembra il tipo di caso in cui una funzione di fabbrica statica può essere più utile di un costruttore. – cdhowie

+0

mi sembra che la terza soluzione possa funzionare. C'è qualche ragione per cui hai scelto di non farlo? Inoltre, perché non usare una libreria come GLFW per caricare il tuo contesto openGL? –

+0

Non ero a conoscenza di GLFW quando ho iniziato. Dà un'occhiata, grazie. –

risposta

12

Comma operator to the rescue! Un'espressione (a, b) valuterà prima a, quindi b.

class DisplayOpenGL { 
public: 
    DisplayOpenGL(HWND hwnd) 
    : m_device(hwnd), 
     m_opengl((m_device.SetPixelFormat(), m_device)) { }; 
private: 
    DeviceContext m_device; 
    OpenGLContext m_opengl; 
}; 
0

Se appartiene a DeviceContext (e sembra così dal tuo codice), chiamalo da DeviceContext c'tor.

1

L'utilizzo di uniqe_ptr per entrambi sembra appropriato qui: è possibile inoltrare DeviceContext e OpenGLContext, invece di includere le intestazioni, ovvero a good thing. Poi questo funziona:

class DisplayOpenGL 
{ 
public: 
    DisplayOpenGL(HWND h); 
private: 
    unique_ptr<DeviceContext> m_device; 
    unique_ptr<OpenGLContext> m_opengl; 
}; 

namespace 
{ 
    DeviceContext* InitDev(HWND h) 
    { 
    DeviceContext* p = new DeviceContext(h); 
    p->SetPixelFormat(); 
    return p; 
    } 
} 

DisplayOpenGL::DisplayOpenGL(HWND h): 
    m_device(InitDev(h)), 
    m_opengl(new OpenGLContext(*m_device)) 
{ 
} 

Se è possibile utilizzare C++ 11 è possibile sostituire InitDev() con un lambda.

1

Se OpenGLContext ha un costruttore 0 discussione e costruttore di copia è possibile modificare il vostro costruttore per

DisplayOpenGL(HWND hwnd) 
: m_device(hwnd) 
{ 
    m_device.SetPixelFormat(); 
    m_opengl = OpenGLContext(m_device); 
}; 

unique_ptr viene generalmente usato quando si vuole fare uno dei membri opzionali o "Null", che si può o potrebbe non voler fare qui.

3

faccio quasi sempre voglia una soluzione basata puntatore intelligente in questi casi comunque?

No. Evitare questa inutile complicazione.

approcci

Due immediati che non sono stati menzionati:

Approccio A:

Il modo pulito.

Creare un piccolo oggetto contenitore per la memoria m_device che chiama SetPixelFormat() nel costruttore. Quindi sostituire DisplayOpenGL ::m_device con un'istanza di quel tipo. Ordine di inizializzazione ottenuto e l'intento è abbastanza chiaro. Illustrazione:

class DisplayOpenGL { 
public: 
    DisplayOpenGL(HWND hwnd) 
     : m_device(hwnd), 
      m_opengl(m_device) { } 
private: 
    class t_DeviceContext { 
    public: 
     t_DeviceContext(HWND hwnd) : m_device(hwnd) { 
      this->m_device.SetPixelFormat(); 
     } 
     // ... 
    private: 
     DeviceContext m_device; 
    }; 
private: 
    t_DeviceContext m_device; 
    OpenGLContext m_opengl; 
}; 

Approccio B:

La rapida & modo sporco. In questo caso puoi utilizzare una funzione statica:

class DisplayOpenGL { 
public: 
    DisplayOpenGL(HWND hwnd) 
    : m_device(hwnd), 
     m_opengl(InitializeDevice(m_device)) { } 
private: 
    // document why it must happen this way here 
    static DeviceContext& InitializeDevice(DeviceContext& pDevice) { 
     pDevice.SetPixelFormat(); 
     return pDevice; 
    } 
private: 
    DeviceContext m_device; 
    OpenGLContext m_opengl; 
}; 
1

Prima di tutto, stai sbagliando. :-) È una pratica molto povera fare cose complesse nei costruttori. Mai. Rendi tali funzioni operative su un oggetto helper che deve essere passato al costruttore. È meglio costruire gli oggetti complessi al di fuori della classe e passarli completamente creati, in questo modo se è necessario trasferirli ad altre classi, è possibile farlo anche nei costruttori THEIR allo stesso tempo. Inoltre in questo modo si ha la possibilità di rilevare gli errori, l'aggiunta di registrazione sensibile, ecc

class OpenGLInitialization 
{ 
public: 
    OpenGLInitialization(HWND hwnd) 
     : mDevice(hwnd) {} 
    void     SetPixelFormat (void)  { mDevice.SetPixelFormat(); } 
    DeviceContext const &GetDeviceContext(void) const { return mDevice; } 
private: 
    DeviceContext mDevice; 
};   

class DisplayOpenGL 
{ 
public: 
    DisplayOpenGL(OpenGLInitialization const &ogli) 
    : mOGLI(ogli), 
     mOpenGL(ogli.GetDeviceContext()) 
     {} 
private: 
    OpenGLInitialization mOGLI; 
    OpenGLContext mOpenGL; 
}; 
0

combinare il Comma operator con un IIFE (Immediately-Invoked Function Expression), che consente di definire le variabili e altre cose complesse non disponibili solo con l'operatore virgola:

struct DisplayOpenGL { 
    DisplayOpenGL(HWND hwnd) 
     : m_device(hwnd) 
     , opengl(([&] { 
      m_device.SetPixelFormat(); 
     }(), m_device)) 
    DeviceContext m_device; 
    OpenGLContext m_opengl; 
}; 
0

L'operatore virgola farebbe piuttosto bene nel tuo caso ma penso che questo problema sia una conseguenza di una cattiva pianificazione delle tue lezioni. Quello che farei è lasciare che i costruttori inizializzino solo lo stato degli oggetti e non le dipendenze (come il contesto di rendering OpenGL). Suppongo che il costruttore di OpenGLContext inizializzi il contesto di rendering di OpenGL e questo è quello che non farei.Invece mi piacerebbe creare il metodo CreateRenderingContext per la classe OpenGLContext di fare l'inizializzazione e anche per chiamare il SetPixelFormat

class OpenGLContext { 
public: 
    OpenGLContext(DeviceContext* deviceContext) : m_device(deviceContext) {} 
    void CreateRenderingContext() { 
     m_device->SetPixelFormat(); 
     // Create the rendering context here ... 
    } 
private: 
    DeviceContext* m_device; 
}; 

... 

DisplayOpenGL(HWND hwnd) : m_device(hwnd), m_opengl(&m_device) { 
    m_opengl.CreateRenderingContext(); 
} 
Problemi correlati