【问题标题】:C++ two phase initializationC++ 两阶段初始化
【发布时间】:2025-12-29 07:05:06
【问题描述】:

我正在编写一个使用两阶段初始化的C++17 GUI library。它位于本机 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&lt;button&gt;(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 不能转换为 std::unique_ptr。由于很多函数都接受 std::unique_ptr 派生类,所以我不能为所有函数添加另一个模板参数,这会使库用户的事情变得复杂。

我想过在析构函数中调用destroy,但后来我读到构造函数和析构函数应该避免调用虚函数,所以我必须在每个析构函数中重新实现它。库用户可能会在派生类中忘记这一点并造成内存泄漏。

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

【问题讨论】:

    标签: c++ initialization unique-ptr


    【解决方案1】:

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

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

    【讨论】: