首页 > 解决方案 > 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,但后来我读到构造函数和析构函数应该避免调用虚函数,所以我必须在每个析构函数中重新实现它。库用户可能会在派生类中忘记这一点并造成内存泄漏。

这个问题有更好的解决方案吗?

标签: c++initializationunique-ptr

解决方案


您可以编写一个“持有者”类来负责创建对象、初始化它、完成它并销毁它。

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

推荐阅读