通常,可以通过更改在类中声明成员的顺序来更改成员初始化程序的运行顺序。但是,有没有办法让基类初始化程序/构造函数不能先运行?
这是我问题的最小草图:
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;
}
答案 0 :(得分:5)
不,这是不可能的。类初始化总是这样的:基类,成员,这个类构造函数。
原因很简单 - 因为您可以在此类构造函数中引用您的成员,所以您必须在调用构造函数之前构造成员。由于您可以从您的成员中引用您的基类成员,因此您必须在此类成员之前构建它们。
答案 1 :(得分:3)
必须首先调用基类构造函数。你不能采用任何其他方法,但你可以设置一种方法来不创建类A基类并首先调用你想要的任何东西。
答案 2 :(得分: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)
答案 3 :(得分:1)
基类始终是完全构造的。没有办法解决这个问题。
一种替代方法是中断继承并将基类移动到子类的成员变量。
成员初始化的顺序是它们在类声明中出现的顺序。
但依赖于此可能会使您的代码变脆,所以请牢记这一点。在课堂宣言中加上适当的评论可能不足以阻止热情的重构者。
答案 4 :(得分:1)
刚刚意识到如何解决问题:
base.super.com.google.common.io
答案 5 :(得分:0)
列表中成员初始值设定项的顺序是无关紧要的:实际的 初始化顺序如下:
1)如果构造函数用于 最派生的类,虚拟基类在中初始化 它们出现在深度优先从左到右遍历的顺序 基类声明(从左到右指的是外观) base-specifier lists)
2)然后,在中初始化直接基类 它们出现在这个类的基本说明符列表中的从左到右的顺序
3)然后,按照以下顺序初始化非静态数据成员 类定义中的声明。
4)最后,身体的 构造函数被执行
来源:http://en.cppreference.com/w/cpp/language/initializer_list
答案 6 :(得分:0)
在您的情况下,反转项目的所有权似乎更为正确:
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);
}
};
答案 7 :(得分:0)
您还可以使用保留的可选参数作为占位符:
class SpecialA : public A {
public:
SpecialA(Arg* arg, std::optional<T> reserved = {}) :
A(reserved = expensiveFunction(arg)),
member(reserved)
{}
private:
T member;
}
它可能比委派构造函数更好,因为当有很多参数时,它会使代码的膨胀减少。另外,委派构造函数可能会产生更多开销(请在错误的情况下纠正我)。