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

问题描述 投票:0回答:9

我有2个资源管理类

DeviceContext
OpenGLContext
都是
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()
    看起来是最干净的解决方案。

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

c++ constructor initialization smart-pointers initialization-list
9个回答
38
投票

逗号运算符来救援! 表达式

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

8
投票

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

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

两个没有提到的直接方法:

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

2
投票

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

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

1
投票

如果

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

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

unique_ptr
通常用于当您想要使其中一个成员可选或“可为空”时,您可能想也可能不想在此处执行此操作。


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。


0
投票

如果它属于

DeviceContext
(从您的代码看来是这样),请从
DeviceContext
c'tor 调用它。


0
投票

逗号运算符IIFE(立即调用函数表达式)相结合,这样您就可以定义仅使用逗号运算符无法实现的变量和其他复杂内容:

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

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

0
投票

我首先想到的是逗号运算符。构造函数链还可以让你整理一些东西。

但是,我认为我已经想出了一种更简洁的方法,使您的意图更加清晰,并且在成员的实际初始化过程中减少混乱 - 这在观察资源管理时很重要。


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

可在此处作为要点

© www.soinside.com 2019 - 2024. All rights reserved.