26

我有 2 个资源管理类DeviceContextOpenGLContext它们都是class DisplayOpenGL. 资源生命周期与DisplayOpenGL. 初始化看起来像这样(伪代码):

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

问题是对 SetPixelFormat() 的调用,因为我不能在DisplayOpenGLc'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;
};

我可以看到的解决方案:

  • 插入m_dummy(m_device.SetPixelFormat())- 不起作用,因为 SetPixelFormat() 没有 retval。(如果它有 retval,你应该这样做吗?)
  • 使用unique_ptr<OpenGLContext> m_opengl;而不是OpenGLContext m_opengl;.
    然后初始化为m_opengl(),在 c'tor 正文中调用 SetPixelFormat() 并使用m_opengl.reset(new OpenGLContext);
  • SetPixelFormat()来自DeviceContextc'tor 的电话

这些解决方案中哪一个更可取,为什么?有什么我想念的吗?

如果重要的话,我在 Windows 上使用 Visual Studio 2010 Express。

编辑:我最感兴趣的是决定其中一种方法所涉及的权衡。

  • m_dummy()不起作用并且看起来不优雅,即使它会
  • unique_ptr<X>对我来说很有趣 - 我什么时候会使用它而不是“普通”X m_x成员?除了初始化问题外,这两种方法在功能上似乎或多或少是等效的。
  • SetPixelFormat()来自c'tor的电话DeviceContext当然有效,但对我来说感觉不干净。DeviceContext应该管理资源并启用它的使用,而不是对用户强加一些随机像素格式策略。
  • stijn's InitDev()看起来是最干净的解决方案。

在这种情况下,我几乎总是想要一个基于智能指针的解决方案吗?

4

8 回答 8

30

逗号运算符来救援!表达式(a, b)a首先计算,然后是b

class DisplayOpenGL {
public:
    DisplayOpenGL(HWND hwnd)
    : m_device(hwnd),
      m_opengl((m_device.SetPixelFormat(), m_device)) { };
private:
    DeviceContext m_device;
    OpenGLContext m_opengl;
};
于 2012-11-09T19:18:57.590 回答
6

在这种情况下,我几乎总是想要一个基于智能指针的解决方案吗?

不。避免这种不必要的并发症。

两种未提及的直接方法:

方法一:

干净的方式。

为在构造函数m_device中调用的存储创建一个小容器对象。SetPixelFormat()然后替换DisplayOpenGL ::m_device为该类型的实例。得到了初始化顺序,意图很明确。插图:

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;
};

方法 B:

快速而肮脏的方式。在这种情况下,您可以使用静态函数:

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;
};
于 2012-11-09T20:55:02.840 回答
1

在这里使用 uniqe_ptr 似乎很合适:您可以转发声明 DeviceContext 和 OpenGLContext,而不是包括它们的标头,这是一件好事)。然后这个工作:

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 ) )
{
}

如果您可以使用 c++11,则可以将 InitDev() 替换为 lambda。

于 2012-11-09T15:54:49.557 回答
1

如果OpenGLContext有 0 参数构造函数和复制构造函数,您可以将构造函数更改为

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

unique_ptr通常在您希望将成员之一设为可选或“可空”时使用,您可能希望也可能不希望在此处执行此操作。

于 2012-11-09T16:47:53.063 回答
1

首先,你做错了。:-) 在构造函数中做复杂的事情是非常糟糕的做法。曾经。在必须传递给构造函数的辅助对象上创建这些操作函数。更好的是在你的类之外构造你的复杂对象并将它们完全创建,这样如果你需要将它们传递给其他类,你也可以同时将它们传递给它们的构造函数。另外,这样您就有机会检测错误、添加合理的日志记录等。

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;
};
于 2012-11-10T06:10:45.013 回答
0

如果它属于DeviceContext(从您的代码中看起来如此),请从DeviceContextc'tor 调用它。

于 2012-11-09T15:51:06.237 回答
0

逗号运算符IIFE(立即调用函数表达式)结合使用,它允许您定义仅使用逗号运算符不可用的变量和其他复杂内容:

struct DisplayOpenGL {
    DisplayOpenGL(HWND hwnd)
        : m_device(hwnd)
        , opengl(([&] {
            m_device.SetPixelFormat();
        }(), m_device))
    DeviceContext m_device;
    OpenGLContext m_opengl;
};
于 2015-03-17T15:48:53.013 回答
0

逗号运算符在您的情况下会做得很好,但我认为这个问题是您的课程计划不当的结果。我要做的是让构造函数只初始化对象的状态而不是依赖项(例如 OpenGL 渲染上下文)。我假设 OpenGLContext 的构造函数初始化了 OpenGL 渲染上下文,这就是我不会做的。相反,我会CreateRenderingContext为 OpenGLContext 类创建方法来进行初始化并调用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();
}
于 2016-11-29T12:54:11.900 回答