You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++17中能否在成员初始化列表完成不可移动对象的两步初始化?

问题背景

我正在使用一套遗留API,其中类A没有默认、拷贝、移动构造函数及赋值运算符,必须通过构造函数+init方法完成两步构造;类B必须依赖完全初始化的A进行构造。我尝试编写类C,其成员包含A和B,期望在C的构造函数成员初始化列表中完成A的两步初始化后再初始化B,但多种尝试均失败,目前用unique_ptr实现,想寻找C17下的更优方案(无法使用C20的保证移动消除规则)。

遗留API代码

class A {
public:
    A(int some_param);

    // 无默认、拷贝、移动构造函数及赋值运算符
    A() = delete;
    A(const A&) = delete;
    A(A&&) = delete;      // 本不该删除,但无法修改该类
    A& operator= (const A&) = delete;
    A& operator= (A&&) = delete;

    // 两步构造:构造后需调用init完成初始化
    void init(int some_other_argument);

    // 其他成员
};

class B {
public:
    // 无默认构造函数,必须传入完全初始化的A
    B() = delete;
    B(const A&);

    // 其他成员
};

目标代码(期望实现的结构)

class C {
public:
    C();
private:
    A m_a;
    B m_b;
};

C::C()
    : m_a{ /* 完成A的两步初始化 */}
    , m_b(m_a)
{}

尝试过的无效方案

  • 立即初始化lambda:编译失败,因为A不可移动
C::C()
    : m_a{[] {
        auto a = A(0xdead_beef);
        a.init(42);
        return a; // 编译错误:A不可移动
    }()}
    , m_b(m_a)
{}
  • 构造函数体赋值:编译失败,因为A和B的默认构造函数已删除,进入函数体前会尝试默认初始化成员
C::C()
    // m_a和m_b会被尝试默认初始化,但它们的默认构造函数已删除
{
    m_a = A(0xdead_beef);
    m_a.init();
    m_b = B(m_a);
}
  • 构造函数体中完成第二步初始化:逻辑错误,初始化m_b时m_a还未完成init,不符合B的构造要求
C::C()
    : m_a(0xdead_beef)
    , m_b(m_a) // 此时m_a未完成init,不符合B的构造条件
{
    m_a.init();
}

当前实现方案

使用std::unique_ptr间接持有A,确保A完成初始化后再构造B:

class C {
public:
    C();
private:
    std::unique_ptr<A> m_a; // 构造函数中保证初始化,无需空指针检查
    B m_b;
};

C::C()
    : m_a{[] {
        auto a = new A(0xdead_beef);
        a->init(42);
        return a;
    }()}
    , m_b(*m_a)
{}

C++17下的更优方案

可以用嵌套类封装A的两步初始化,利用类成员的初始化顺序(嵌套类对象先于外部类的B成员初始化),避免动态内存分配:

class C {
private:
    // 嵌套类:负责完成A的两步初始化
    class AHolder {
    public:
        A a;
        AHolder() : a(0xdead_beef) {
            a.init(42);
        }
    };

    AHolder m_a_holder;
    B m_b;
public:
    C() : m_b(m_a_holder.a) {}

    // 对外暴露A的接口(如果需要)
    A& get_a() { return m_a_holder.a; }
    const A& get_a() const { return m_a_holder.a; }
};

方案优势

  1. 避免了unique_ptr带来的动态内存分配和间接访问开销
  2. 严格保证A完成初始化后才构造B:m_a_holder作为C的第一个成员,会先被初始化,其构造函数中完成A的init,之后再初始化m_b
  3. 完全符合C++17标准,无需依赖后续版本特性

内容的提问来源于stack exchange,提问作者Robin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 20:25:18