在C++中,在执行一些其他指令后调用基类构造函数

25
据我所知,无法调用基类的构造函数。我所知道的唯一方法是这样的:
MyClass::MyClass(/* args */) : Base(/* args */)
{
   // ...
}

但这样会在开头调用构造函数。有没有办法在构造函数中的其他位置调用它?像这样:

MyClass::MyClass(/* args */)
{
   // ... instructions
   Base::Base(/* args */);
   // ... other_instructions
}

根据这个问题“如何调用超类构造函数的规则是什么?”,我了解到除此之外没有其他方法,但我在这里读到似乎是可能的,但如果我尝试,会得到以下结果:
error: invalid use of 'class Base'.

我做错了什么吗?有没有其他方法或解决方案来满足这个需求?

谢谢!

编辑:我明白我忘记了一个关键点:基类是框架的一部分,因此如果可能的话最好不要修改它。


1
问题是:你为什么需要这个? - Björn Pollex
由于某些原因,我无法重新标记此问题,我认为添加OOP作为标记是合适的。 - amit
我需要这个是因为我需要修改Qt框架中的一个类,但我想避免直接修改Qt源代码。似乎小部件的构造函数会执行一些导致调用Qt驱动程序中某个方法的操作。在此调用发生之前,我希望已经初始化了我添加的其他字段。 - Luca Carlon
在我的看法中,这是完全有效的,同时也是C ++中最奇怪的事情之一,你不能这么做。在Swift中,编译器强制要求在调用超类构造函数之前必须实例化子类的局部变量。 - Trenskow
7个回答

20
如果基类构造函数至少需要一个参数,您可以使用以下帮助函数:
int DoStuffBeforeCtorAndForwardInt(int arg, Foo foo)
{
    DoStuff(arg, foo);
    return arg;
}

MyClass::MyClass(int arg, Foo foo)
    : Base(DoStuffBeforeCtorAndForwardInt(arg, foo))
{
   // ...
}

如果你希望默认初始化基类,可以使用复制构造函数来复制一个默认初始化的基类实例:

Base DoStuffBeforeCtorAndReturnDefaultBase(int arg, Foo foo)
{
    DoStuff(arg, foo);
    return Base();
}

MyClass::MyClass(int arg, Foo foo)
    : Base(DoStuffBeforeCtorAndReturnDefaultBase(arg, foo))
{
   // ...
}

或者,如果Base不必是第一个基类,你可以从一个辅助类派生出MyClass

MyClass::MyClass(/* ... */)
    : DoStuffHelperClass(/* ... */),
    Base(/* ... */)
{
   // ...
}

以上所有方法都要求你所做的"事情"不依赖于即将被初始化的对象(即函数不能安全地成为成员函数,也不能将this作为参数安全地传递给它们)。

这意味着你可以记录一些日志或类似的操作,但是在基类初始化之后也可以做这些操作。

(编辑 除了使用 DoStuffHelperClass 解决方案之外,你当然可以在 DoStuffHelperClass 中拥有成员,访问它们等等)


尽管我必须说我不能回忆起曾经使用/需要/想要类似的东西。很可能有另一种(更好的)解决方案来实现你想要做的事情。


我不知道这些解决方案是否实际适用于我所尝试的内容,但无论如何,对我来说,每个答案都是非常好的!谢谢! - Luca Carlon

10

使用base-from-member惯用语在“真正”的基类(即Base)的构造函数之前运行您的代码:

struct Base {
  Base(string, int);
};

struct DerivedDetail {
  DerivedDetail() {
    value = compute_some_value();
    value += more();
    value += etc();
    other = even_more_code(value);
  }
  string value;
  int other;
};

struct Derived : private DerivedDetail, Base {
  Derived() : Base(value, other) {}
  // In particular, note you can still use this->value and just
  // ignore that it is from a base, yet this->value is still private
  // within Derived.
};

即使您不在DerivedDetail中有实际成员,此方法仍可使用。 如果您提供更多关于在Base构造函数之前必须执行的操作的详细信息,则我可以给出更好的示例。


8

在构建您自己的类之前,基类始终完全构建。如果您需要更改基类的状态,则必须在其构建完成后明确执行该操作。

示例:

MyClass::MyClass()
{
    // Implicit call to Base::Base()

    int result = computeSomething();
    Base::setResult(result);

    // ... 
}

2
除了已经编写的解决方案之外,您还可以使用静态构造函数并将MyClass的构造函数设置为私有。
class QtBase{
  // ...
};

class MyClass : public QtBase{
public:
  // copy ctor public
  MyClass(MyClass const& other);

  static MyClass Create(/*args*/){
    // do what needs to be done
    int idata;
    float fdata;
    // work with idata and fdata as if they were members of MyClass
    return MyClass(idata,fdata); // finally make them members
  }

  static MyClass* New(/*args*/){
    int idata;
    float fdata;
    // work with idata and fdata as if they were members of MyClass
    return new MyClass(idata,fdata); // finally make them members
  }

private:
  // ctor private
  MyClass(int a_idata, float a_fdata)
    : idata(a_idata)
    , fdata(a_fdata)
  {}

  int idata;
  float fdata;
};

现在你需要创建MyClass的实例,可以使用以下方式之一:
MyClass obj = MyClass::Create(/*args*/);

或者

MyClass* ptr = MyClass::New(/*args*/);

顺便说一下:这被称为“命名构造函数惯用法” ;) - Paul Groke
如果我理解正确的话,如果我不需要初始化子类的成员,这将是一个解决方案。但是使用这个解决方案,我不能这样做,对吗?谢谢! - Luca Carlon
@Luca:嗯,你总是可以有一个构造函数,它已经初始化了它的成员。 :) - Xeo
抱歉,这意味着我不理解这个解决方案 :-) 如果我在返回语句中实例化MyClass,在“做必要的事情”中,我无法访问MyClass实例的成员。但也许我完全错过了重点...你能举个例子吗?感谢你的帮助! - Luca Carlon
@Luca:稍微编辑了一下答案,添加了虚拟成员数据。我的意思是,你准备好成员数据并像注释中所示的那样传递给构造函数。 - Xeo
啊,我明白了!好的!这也看起来不错!另一种可能的解决方案!谢谢! - Luca Carlon

0
不行,因为它不会保证类型安全。 考虑你有:一个类A和一个变量A.var。 现在考虑B从A继承,并在A初始化之前使用var。你将得到运行时错误!语言希望防止这种情况发生,因此必须先初始化超类构造函数。

0

不,你不能那样做,正如其他人在之前的回答中所描述的那样。

你唯一的机会是组合,也就是说MyClassBase类作为成员字段使用:

class MyClass {
public:
   /** the methods... */

private:
   Base* _base;
};

所以你可以稍后初始化_base,当你有所需的信息时。无论如何,我不知道这是否适用于你的情况。


不幸的是,我认为我的唯一可能性就是创建一个实际的子类。被调用的方法将创建的实例作为参数传递,而我需要使用我的子类。 - Luca Carlon

-1
不可能,因为构造函数的调用顺序是由标准严格定义的。在派生类构造函数执行之前,必须先执行基类构造函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接