C ++ - 在初始化类成员之前运行一个函数

时间:2012-11-09 15:43:08

标签: c++ constructor initialization smart-pointers initialization-list

我有2个资源管理类DeviceContextOpenGLContext都是class DisplayOpenGL的成员。资源生命周期与DisplayOpenGL相关联。初始化看起来像这样(伪代码):

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

问题是调用SetPixelFormat(),因为我无法在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;
};

我可以看到的解决方案:

  • 插入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() c'tor
  • 致电DeviceContext

哪种解决方案更可取?为什么?我缺少什么?

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

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

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

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

8 个答案:

答案 0 :(得分:15)

Comma operator to the rescue!表达式(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;
};

答案 1 :(得分:4)

  

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

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

两个未提及的直接方法:

方法A:

干净的方式。

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:

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

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

答案 2 :(得分:1)

在这里使用uniqe_ptr似乎是合适的:你可以转发声明DeviceContext和OpenGLContext,而不是包括它们的标题,即a good thing)。然后这工作:

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,你可以用lambda替换InitDev()。

答案 3 :(得分:1)

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

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

unique_ptr通常用于您希望将其中一个成员设为可选或“可空”时,您可能想要或可能不想在这里做。

答案 4 :(得分: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;
};

答案 5 :(得分:0)

如果它属于DeviceContext(从您的代码中看起来如此),请从DeviceContext c&#39}调用它。

答案 6 :(得分:0)

Comma operatorIIFE (Immediately-Invoked Function Expression)结合使用,这样您就可以使用逗号运算符定义变量和其他复杂内容:

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

答案 7 :(得分:0)

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