是否可以在基类构造函数之前运行成员初始值设定项

Is it possible to run member initializers before base-class constructor?

本文关键字:成员 运行 基类 构造函数 是否      更新时间:2023-10-16

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

这是我的问题的一个最简单的草图:

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;
}

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

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

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

并非如此。

也就是说,这里有几个解决方案:

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)

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

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;
}

必须首先调用基类构造函数。你不能采取任何其他方法,但你可以设置一种方法,不使类成为基类,并首先调用你想要的任何东西。

基类总是首先完全构造。这是没有办法的。

一种选择是打破继承,将基类移动到子类的成员变量。

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

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

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

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

2) 然后,直接基类在中初始化从左到右的顺序,因为它们出现在这个类的基本说明符列表中

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

4) 最后构造函数执行

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

在您的情况下,反转项目的所有权看起来更正确:

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

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

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

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