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

kuarbcqp  于 2023-08-09  发布在  其他
关注(0)|答案(9)|浏览(107)

我有两个资源管理类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);

  • DeviceContext控制器调用SetPixelFormat()

这些解决方案中哪一个更可取,为什么?我错过了什么吗?
我在Windows上使用Visual Studio 2010 Express,如果有关系的话。

**编辑:**我最感兴趣的是在决定这些方法之一时所涉及的权衡。

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

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

ih99xse1

ih99xse11#

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

字符串

a6b3iqyw

a6b3iqyw2#

在这种情况下,我是否总是想要一个基于智能指针的解决方案?
不。避免不必要的麻烦。
有两个尚未提及的直接方法:

方法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:

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

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

eqqqjvef

eqqqjvef3#

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

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

字符串

atmip9wb

atmip9wb4#

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

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

字符串
unique_ptr通常用于使其中一个成员为可选或“可空”的情况,在这里您可能希望这样做,也可能不希望这样做。

balp4ylt

balp4ylt5#

在这里使用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()。

wmvff8tz

wmvff8tz6#

如果它属于DeviceContext(从代码中看似乎是这样),则从DeviceContext c'tor调用它。

elcex8rz

elcex8rz7#

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

字符串

xpcnnkqh

xpcnnkqh8#

逗号操作符在您的情况下会做得很好,但我认为这个问题是您的类计划不好的结果。我要做的是让构造函数只初始化对象的状态,而不初始化依赖项(比如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();
}

字符串

tp5buhyn

tp5buhyn9#

逗号运算符是我首先想到的。构造函数链也可以让你稍微整理一下。
然而,我认为我已经提出了一种更整洁的方法,使您的意图清晰,并且在您的成员的真实的初始化周围产生较少的混乱-这在观察资源管理时很重要。

// Inherit from me privately.
struct ConstructorPreInitialisation{
    // Pass in an arbitrary lambda function here, it will simply be discarded
    // This remoes the need for a comma operator and importantly avoids cluttering your
    // real initialisation of member subobjects.
    inline ConstructorPreInitialisation( [[maybe_unused]] const auto λ ){ λ(); }
};
// WARN: This may increase the size of your class using it
// The compiler can probably elide this but from memory objects with zero length are not permitted
// Have not checked the fine details against the standard
// Therefore this should not be used if this is an unacceptable condition


// Usage
// Example class originally from: https://en.cppreference.com/w/cpp/language/constructor#Example

#include <fstream>
#include <string>
#include <mutex>
 
struct Base
{
    int n;
};   
 
struct Class : public Base, private ConstructorPreInitialisation
{
    unsigned char x;
    unsigned char y;
    std::mutex m;
    std::lock_guard<std::mutex> lg;
    std::fstream f;
    std::string s;
 
    Class(int x) 
      : Base{123}, // initialize base class
      
        ConstructorPreInitialisation([&](){
            // Call some global allocation here, for example.
        }),
        
        x(x),     // x (member) is initialized with x (parameter)
        y{0},     // y initialized to 0
        f{"test.cc", std::ios::app}, // this takes place after m and lg are initialized
        s(__func__), // __func__ is available because init-list is a part of constructor
        lg(m),    // lg uses m, which is already initialized
        m{}       // m is initialized before lg even though it appears last here
    {}            // empty compound statement
 
};

字符串
可作为gist here提供

相关问题