【问题标题】:Is it possible to run member initializers before base-class constructor?是否可以在基类构造函数之前运行成员初始化程序?
【发布时间】:2026-02-03 07:55:01
【问题描述】:

通常可以通过更改成员在类中声明的顺序来更改成员初始化程序的运行顺序。但是,有没有办法让基类初始化器/构造器不先运行?

这是我的问题的最小草图:

class SpecialA : public A {
public:
    explicit SpecialA(Arg* arg)
        : member(expensiveFunction(arg))
        , A(member) // <-- This will run first but I don't want it to
    {}
private:
    T member;
}

【问题讨论】:

  • 将这些成员移动到另一个类中,只是为了容纳这些成员。然后从成员持有类和“基”类继承。
  • 为什么还需要这样做?你可以传递一个const T &amp;arg,比如SpecialA(expensiveFunction(some_value)),避免整个问题。
  • @owacoder 我在构造函数中需要Arg* arg,原因已从示例中剥离,并将expensiveFunction() 限制在此范围内作为内部实现细节简化了类间设计。
  • @tennenrishin - 啊,我明白了。好吧,它一个解决方案,虽然有点老套。 ;)

标签: c++ initialization


【解决方案1】:

不,这是不可能的。类的初始化总是这样:基类、成员、这个类的构造函数。

原因很简单——因为你可以在这个类的构造函数中引用你的成员,你必须在调用你的构造函数之前构造成员。由于您可以从您的成员中引用您的基类成员,因此您必须在此类成员之前构造它们。

【讨论】:

    【解决方案2】:

    是否可以在基类构造函数之前运行成员初始化程序?

    不是这样的。

    也就是说,这里有一些解决方案:

    1:将成员移动到人工基地:

    template<typename T>
    class InitializerBase {
    protected:
        InitializerBase(T&& value) : member(std::move(value)) {}
    
        T member;
    };
    
    class SpecialA: public InitializerBase<T>, public A {
    public:
        SpecialA(Arg *arg) : InitializerBase<T>(expensiveFunction(arg)): A{}
        {
        }
    };
    

    (可能)最好的解决方案如下所示:

    2:对完全构造的值使用依赖注入(这是最安全、最好的设计,除非你的代码有更大的问题,否则最有效的实现):

    class SpecialA : public A {
    public:
        explicit SpecialA(T fully_computed_value)
            : A(fully_computed_value)
            , member(std::move(fully_computed_value)) // no heavy computations performed
        {}
    private:
        T member;
    }
    

    建筑:

    auto &a = SpecialA(expensiveFunction(arg));
    

    这是最好的解决方案,因为如果expensiveFunction 抛出,您甚至不会开始构造结果对象(应用程序不必为半构造的 SpecialA 释放资源)。

    编辑:也就是说,如果你想隐藏昂贵功能的使用,那么规范的解决方案(规范是因为它是可重用的、简约的并且仍然尊重良好的设计)是添加一个工厂函数:

    SpecialA make_special(Arg *arg)
    {
        auto result = expensiveFunction(arg);
        // extra steps for creation should go here (validation of the result for example)
        return SpecialA( std::move(result) );
    }
    

    客户端代码:

    auto specialA = make_special(arg); // simplistic to call (almost as simple
                                       // as calling the constructor directly);
                                       // hides the use of the expensive function
                                       // and provides max. reusability (if you
                                       // need to construct a SpecialA without
                                       // calling the expensive function, you 
                                       // can (by obtaining the constructor argument
                                       // for SpecialA in a different way)
    

    【讨论】:

    • 我也会选择后者,但 OP 提到他们希望昂贵的功能成为内部实现。
    • (在您的第一个解决方案中)您模板化以避免对某些类型的假设,但随后假设返回类型为expensiveFunction。由于您正在硬编码member 的名称,您不妨硬编码它的类型。但也许您不应该假设 expensiveFunction 返回的类型,因此应该将其模板化以实现完美转发。
    • 我认为这些假设并不重要;事实上,我写的代码不会编译 :);这只是一个粗略的蓝图/伪代码,仅足以说明我的观点(因为这就是 OP 问题中的代码的编写方式)
    【解决方案3】:

    刚刚意识到如何解决问题:

    class SpecialA : public A {
    public:
        explicit SpecialA(Arg* arg) : SpecialA(arg, expensiveFunction(arg)) {}
    private:
        SpecialA(Arg* arg, T&& member) : A(member) , member(member) {};
        T member;
    }
    

    【讨论】:

      【解决方案4】:

      必须首先调用基类构造函数。你不能采取任何其他方式,但你可以设计一种不创建类 A 基类并首先调用任何你想要的方法。

      【讨论】:

        【解决方案5】:

        基类总是首先完全构造。没有办法解决这个问题。

        另一种方法是中断继承并将基类移动到子类的成员变量中。

        成员初始化的顺序就是它们在类声明中出现的顺序。

        但是依赖它会使您的代码变得脆弱,所以请记住这一点。在类声明中添加合适的注释可能不足以阻止热情的重构者。

        【讨论】:

          【解决方案6】:

          列表中成员初始化器的顺序无关紧要:实际 初始化顺序如下:

          1) 如果构造函数是 最派生类,虚拟基类在 它们出现在深度优先从左到右遍历中的顺序 基类声明(从左到右是指在 基本说明符列表)

          2) 然后,直接基类在 在此类的基本说明符列表中出现的从左到右的顺序

          3) 然后,非静态数据成员按顺序初始化 类定义中的声明。

          4) 最后, 构造函数被执行

          来源:http://en.cppreference.com/w/cpp/language/initializer_list

          【讨论】:

            【解决方案7】:

            在您的情况下,反转项目的所有权似乎更正确:

            struct A
            {
            protected:
              A(T expensive_object) : _expensive_object(std::move(expensive_object)) {}
            
            protected:
              T& access_expensive_object() {
                return _expensive_object;
              }
            private:
              T _expensive_object;
            };
            
            class SpecialA : public A {
            public:
                explicit SpecialA(Arg* arg)
                    : A(expensiveFunction(arg))
                {}
            
                void use_object() {
                  auto& o = expensive_object();
                  do_something_with(o);
                }
            };
            

            【讨论】:

              【解决方案8】:

              您也可以使用保留的可选参数作为占位符:

              class SpecialA : public A {
              public:
                  SpecialA(Arg* arg, std::optional<T> reserved = {}) : 
                      A(reserved = expensiveFunction(arg)),
                      member(reserved)
                  {}
              private:
                  T member;
              }
              

              它可以比委托构造函数更好,因为当有很多参数时它会减少代码膨胀。此外,委托构造函数可能会产生更多开销(如果我错了,请纠正我)。

              【讨论】: