C++ - 在初始化类成员之前运行函数
C++ - Run a function before initializing a class member
我有 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。(如果它有 reval,你应该这样做吗? - 使用
unique_ptr<OpenGLContext> m_opengl;
而不是OpenGLContext m_opengl;
。
然后初始化为m_opengl()
,在 c'tor 主体中调用 SetPixelFormat() 并使用m_opengl.reset(new OpenGLContext);
- 从
DeviceContext
c'tor拨打SetPixelFormat()
这些解决方案中哪一个更可取,为什么?我错过了什么?
我正在Windows上使用Visual Studio 2010 Express,如果它很重要的话。
编辑:我最感兴趣的是决定其中一种方法所涉及的权衡。
m_dummy()
不起作用,即使它会unique_ptr<X>
对我来说很有趣 - 我什么时候会使用它而不是"普通"X m_x
成员?除了初始化问题之外,这两种方法在功能上似乎或多或少是等效的。- 从
DeviceContext
c'tor 打电话给SetPixelFormat()
当然有效,但对我来说感觉不干净。DeviceContext
应该管理资源并启用其使用,而不是对用户施加一些随机像素格式策略。 - Stijn的
InitDev()
看起来是最干净的解决方案。
在这种情况下,我几乎总是想要一个基于智能指针的解决方案吗?
逗号运算符来救援!表达式(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;
};
在这种情况下,我几乎总是想要一个基于智能指针的解决方案吗?
不。避免这种不必要的复杂化。
尚未提及的两种直接方法:
方法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;
};
首先,你做错了。 :-) 在构造函数中做复杂的事情是非常糟糕的做法。 曾。 使这些操作在必须传递到构造函数的帮助程序对象上函数。 更好的方法是在类之外构造复杂对象并将它们完全创建,这样如果您需要将它们传递给其他类,您也可以同时将它们传递给 ITS 构造函数。 此外,这样你就有机会检测错误,添加合理的日志记录等。
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;
};
如果OpenGLContext
有一个 0 参数构造函数和复制构造函数,您可以将构造函数更改为
DisplayOpenGL(HWND hwnd)
: m_device(hwnd)
{
m_device.SetPixelFormat();
m_opengl = OpenGLContext(m_device);
};
unique_ptr
通常在您希望使其中一个成员可选或"可为空"时使用,您可能希望在此处执行此操作,也可能不希望这样做。
在这里对两者都使用 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,你可以用 lambda 替换 InitDev()。
如果它属于DeviceContext
(从你的代码来看似乎是这样),请从DeviceContext
c'tor调用它。
将逗号运算符与 IIFE(立即调用的函数表达式)结合使用,它允许您定义变量和其他仅使用逗号运算符不可用的复杂内容:
struct DisplayOpenGL {
DisplayOpenGL(HWND hwnd)
: m_device(hwnd)
, opengl(([&] {
m_device.SetPixelFormat();
}(), m_device))
DeviceContext m_device;
OpenGLContext m_opengl;
};
逗号运算符在您的情况下会做得很好,但我认为这个问题是您的类规划不善的结果。我要做的是让构造函数只初始化对象的状态,而不是依赖项(例如 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();
}
逗号运算符是我想到的第一件事。构造函数链接还可以让你稍微整理一下。
但是,我认为我已经想出了一种更整洁的方法,使您的意图清晰,并且围绕成员的实际初始化减少混乱 - 这在监视资源管理时很重要。
// 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
};
可在此处作为要点提供
- 无法获取菜单选择以运行函数.C++
- 为什么std::async使用同一个线程运行函数
- MINGW - 正确运行函数所需的 cdecl
- 如何在 C 中使用空的 main() 方法运行函数?
- 运行函数作为 constexpr 和不作为 constexpr
- 如何在全局变量的构造函数之前运行函数
- 在 Cap'n Proto RPC 服务器中定期运行函数
- 为什么当我在 c++ 中运行函数时,我的代码显示数字 53
- CLang:在 std::thread 中运行函数会导致结构创建BAD_ACCESS
- 如何使用 TBB 在单个线程中运行函数
- 我在运行函数 GetVolumeInformation() 时得到非常随机的结果
- 如何使用 boost::asio io_service 运行函数异步
- Qt - 在其他线程上运行函数
- 在一组模板化对象上运行函数
- pybind11:属性错误:尝试从 py 文件运行函数时,模块'XXX'没有属性'YYY'
- 添加按钮以通知运行函数的通知
- 如何在程序后台运行函数(特别是自动保存函数)?QT / C++
- Qt - 在单独的线程中运行函数
- 如何在单独的线程上运行函数(如果线程可用)
- 如何发出编译和运行C++函数的 LLVM IR