c++ - C++ 两阶段初始化
问题描述
我正在编写一个使用两阶段初始化的C++17 GUI 库。它位于本机 GUI API(目前:Win32)之上,并向用户隐藏其复杂性。
将 Windows 映射到 C++ 类通常需要两个阶段初始化:第一阶段创建 C++ 对象,第二阶段创建 API 对象。
这是必需的,因为订阅窗口事件的第一个机会是在构造函数内部。因此,如果您的基本构造函数在您订阅事件之前已经创建了窗口,那么您会错过一些。除此之外,不应从 C++ 中的构造函数调用虚函数,因此没有办法绕过它。
我想使用以下模式来确保 Windows 资源(窗口、GDI 句柄等)的两个阶段初始化:
所有使用资源的函数都有 create() 和 destroy() 成员函数,它们是从基本模板类派生的。
template <class T>
class resource {
public:
virtual T* create()=0;
virtual void destroy()=0;
}
然后我可以从中派生类:
class wnd : resource<wnd> {
// do the magic
}
要创建这些类,我想创建一个执行以下操作的全局库函数:
template <class T, typename... A>
// TODO: Need a concept here, but not available yet.
static std::unique_ptr<T> create(A... args)
{
T* ptr = new T(args...);
ptr->create();
return std::unique_ptr<T>(ptr);
}
所以我会这样称呼它,auto button=::create<button>(ctor args)
然后它会将参数传递给 ctor,将 unique_ptr 返回给我的按钮并在其上调用 create 函数。
现在我也想实施销毁。它将作为自定义删除器附加到 unique_ptr 并调用 destroy()。这就是我 - 在理论上 - 认为它应该工作的方式。
// --- two phase construction pattern ---
template <typename T>
struct destroy {
void operator()(T* p) { p->destroy(); delete p; }
};
template <class T, typename... A>
static std::unique_ptr<T> create(A... args)
{
T* ptr = new T(args...);
ptr->create();
return std::unique_ptr<T, destroy<T>>(ptr);
}
问题是这失败了,因为std::unique_ptr<T, destroy> 不能转换为 std::unique_ptr<T,std::default_delete)。由于很多函数都接受std::unique_ptr派生类,所以我不能为所有函数添加另一个模板参数,这会使库用户的事情变得复杂。
我想过在析构函数中调用destroy,但后来我读到构造函数和析构函数应该避免调用虚函数,所以我必须在每个析构函数中重新实现它。库用户可能会在派生类中忘记这一点并造成内存泄漏。
这个问题有更好的解决方案吗?
解决方案
您可以编写一个“持有者”类来负责创建对象、初始化它、完成它并销毁它。
#include <iostream>
#include <memory>
template<class base>
class holder {
public:
template<class T, class... Args>
static holder createAndInitialize(Args... args) {
holder h = create<T, Args...>(args...);
h.initialize();
return h;
}
// to create but not initialize
template<class T, class... Args>
static holder create(Args... args) {
return holder(new T(args...));
}
void initialize() {
if (!m_initialized)
{
mres->initialize();
m_initialized = true;
}
}
void finalize()
{
if (m_initialized) {
mres->finalize();
m_initialized = false;
}
}
// access to the underlying pointer
const base *ptr() const {
return mres.get();
}
base *ptr() {
return mres.get();
}
~holder() {
finalize();
std::cout << "holder destructor" << std::endl;
}
private:
holder(base * res)
: mres(res), m_initialized(false)
{ }
holder(holder &&rhs)
: mres(std::move(rhs.mres)),
m_initialized(rhs.m_initialized)
{ }
std::unique_ptr<base> mres;
bool m_initialized;
};
class resource {
private: // Make these private so that clients cannot invoke directly
virtual void initialize()=0;
virtual void finalize()=0;
public:
virtual ~resource() {}
virtual void doSomething()=0;
friend class holder<resource>;
};
class wnd : public resource {
public:
void initialize() override {
std::cout << "in wnd " << mv << " initialize" << std::endl;
}
void finalize() override {
std::cout << "in wnd " << mv << " finalize" << std::endl;
}
void doSomething() override {
std::cout << "in wnd " << mv << " doSomething" << std::endl;
}
~wnd() {
std::cout << "in wnd " << mv << " destructor" << std::endl;
}
private:
wnd(int v)// make constructor private
: mv(v)
{
std::cout << "in wnd " << v << " constructor" << std::endl;
}
friend class holder<resource>; // add holder as friend
int mv;
};
// class which has some child resources
class wnd2 : public resource {
public:
// initialize the child objects
void initialize() override {
std::cout << "in wnd2 initialize" << std::endl;
mres1.initialize();
mres2.initialize();
}
// finalize the child objects
void finalize() override {
mres1.finalize();
mres2.finalize();
std::cout << "in wnd2 finalize" << std::endl;
}
void doSomething() override {
std::cout << "in wnd2 doSomething" << std::endl;
}
~wnd2() {
std::cout << "wnd2 destructor" << std::endl;
}
private:
// In the constructor, the child objects are only created and not initialized
wnd2()
: mres1(holder<resource>::create<wnd>(1)),
mres2(holder<resource>::create<wnd>(2))
{ }
holder<resource> mres1;
holder<resource> mres2;
friend class holder<resource>; // add holder as friend
};
int main()
{
holder<resource> h2 = holder<resource>::createAndInitialize<wnd2>();
h2.ptr()->doSomething();
}
推荐阅读
- python - 重建数据库 ViewVC
- google-bigquery - 在 bigquery 中跨项目复制数据时性能偶尔会降低
- python - python中异步任务和同步线程之间的通信
- python - 数据框按值分组,删除重复项,但保存不相似的条目?Python
- php - 编辑 Wordpress webpack 构建的 wp-config
- reactjs - 如何将参数发送到 ContractForm 中的方法
- r - Docker 是否足以满足大约 100 个连接的 Shiny 应用程序,还是我需要 Shiny Proxy?
- python - NS3 - python.h 文件无法定位编译错误
- splunk - 如何构造一个 splunk 查询以生成字段为空或不为空的事件计数?
- c# - 如何从 Visual Studio 2019 Mac 上的操作自动创建视图页面