c++ - programacion - que es un metodo en poo
C++-Ejecutar una funciĆ³n antes de inicializar un miembro de clase (8)
Tengo 2 clases de administración de recursos, DeviceContext
y OpenGLContext
ambos miembros de la class DisplayOpenGL
. Los tiempos de vida de los recursos están vinculados a DisplayOpenGL
. La inicialización se ve así (pseudo código):
DeviceContext m_device = DeviceContext(hwnd);
m_device.SetPixelFormat();
OpenGLContext m_opengl = OpenGLContext(m_device);
El problema es la llamada a SetPixelFormat (), ya que no puedo hacer eso en la lista de inicializadores del c''tor DisplayOpenGL
:
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;
};
Soluciones que puedo ver:
- Inserción de
m_dummy(m_device.SetPixelFormat())
: no funcionará ya que SetPixelFormat () no tiene retval. (¿Deberías hacer esto si tuviera una retval?) - Use
unique_ptr<OpenGLContext> m_opengl;
en lugar deOpenGLContext m_opengl;
.
Luego inicialice comom_opengl()
, llame a SetPixelFormat () en el cuerpo del administrador y usem_opengl.reset(new OpenGLContext);
- Llame a
SetPixelFormat()
desdeDeviceContext
c''tor
¿Cuál de estas soluciones es preferible y por qué? ¿Algo me falta?
Estoy usando Visual Studio 2010 Express en Windows, si importa.
Edit: Estoy más interesado en las concesiones involucradas en la decisión de uno de estos métodos.
-
m_dummy()
no funciona y parece poco elegante incluso si lo hiciera -
unique_ptr<X>
es interesante para mí: ¿cuándo lo usaría en lugar de un miembroX m_x
"normal"? Los dos métodos parecen ser funcionalmente más o menos equivalentes, excepto por los problemas de inicialización. - Llamar a
SetPixelFormat()
desde el dispositivoDeviceContext
ciertamente funciona, pero me parece impuro.DeviceContext
debe administrar el recurso y habilitar su uso, no imponer una política de formato de píxeles aleatorios a los usuarios. - stijn''s
InitDev()
stijn''sInitDev()
parece la solución más limpia.
De todas formas, ¿siempre quiero una solución basada en punteros inteligentes en estos casos?
De todas formas, ¿siempre quiero una solución basada en punteros inteligentes en estos casos?
No. Evita esta complicación innecesaria.
Dos enfoques inmediatos que no han sido mencionados:
Enfoque A:
La forma limpia.
Cree un pequeño objeto contenedor para el almacenamiento de SetPixelFormat()
que llame a SetPixelFormat()
en el constructor. Luego reemplace DisplayOpenGL ::m_device
con una instancia de ese tipo. Orden de inicialización obtenida, y la intención es bastante clara. Ilustración:
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;
};
Enfoque B:
La forma rápida y sucia. Puedes usar una función estática en este caso:
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;
};
Combine el operador de coma con un IIFE (expresión de función de invocación inmediata) , que le permite definir variables y otras cosas complejas que no están disponibles solo con el operador de coma:
struct DisplayOpenGL {
DisplayOpenGL(HWND hwnd)
: m_device(hwnd)
, opengl(([&] {
m_device.SetPixelFormat();
}(), m_device))
DeviceContext m_device;
OpenGLContext m_opengl;
};
El operador de coma lo haría bastante bien en su caso, pero creo que este problema es una consecuencia de una mala planificación de sus clases. Lo que haría es permitir que los constructores solo inicialicen el estado de los objetos y no las dependencias (como el contexto de representación OpenGL). Supongo que el constructor de OpenGLContext inicializa el contexto de representación de OpenGL y eso es lo que no haría. En su lugar, crearía el método CreateRenderingContext
para que la clase OpenGLContext realice la inicialización y también llame al 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();
}
El uso de uniqe_ptr para ambos parece apropiado aquí: puede reenviar declarar DeviceContext y OpenGLContext, en lugar de incluir sus encabezados, lo que es una buena cosa ). Entonces esto funciona:
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 ) )
{
}
Si puede usar c ++ 11, puede reemplazar InitDev () con un lambda.
En primer lugar, lo estás haciendo mal. :-) Es una mala práctica hacer cosas complejas en los constructores. Siempre. Realice esas funciones de operaciones en un objeto auxiliar que debe pasarse al constructor en su lugar. Mejor es construir sus objetos complejos fuera de su clase y pasarlos totalmente creados, de esa manera, si necesita pasarlos a otras clases, puede hacerlo también en SUS constructores al mismo tiempo. Además, de esa manera tiene la posibilidad de detectar errores, agregar un registro sensible, etc.
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;
};
Si OpenGLContext
tiene un constructor de 0 argumentos y un constructor de copia, puede cambiar su constructor a
DisplayOpenGL(HWND hwnd)
: m_device(hwnd)
{
m_device.SetPixelFormat();
m_opengl = OpenGLContext(m_device);
};
unique_ptr
se usa generalmente cuando usted quiere que uno de los miembros sea opcional o "anulable", lo que puede o no desear hacer aquí.
Si pertenece a DeviceContext
(y parece estarlo en tu código), llámalo desde DeviceContext
c''tor.
Operador de coma al rescate! Una expresión (a, b)
evaluará a
primera, luego b
.
class DisplayOpenGL {
public:
DisplayOpenGL(HWND hwnd)
: m_device(hwnd),
m_opengl((m_device.SetPixelFormat(), m_device)) { };
private:
DeviceContext m_device;
OpenGLContext m_opengl;
};