如何从基类构造函数调用派生类虚拟方法

How to call derived class virtual method from base class constructor?

本文关键字:派生 虚拟 方法 函数调用 基类      更新时间:2023-10-16
class MyClass1
{
public:
  MyClass1()
  {
    init();
  }
  virtual void init()
  {
    printf("MyClass1 init");
  }
}
class MyClass2 : public MyClass1
{
public:
  virtual void init()
  {
    printf("MyClass2 init");
  }
}
int main()
{
  MyClass2 *obj = new MyClass2();
  return 0;
}

我希望这个结果

"MyClass2 init"

但它实际上显示了消息

"MyClass1 init"

如何从基类构造函数调用派生类虚拟方法?

=== 更新1 ===

class MyClass1
{
public:
  MyClass1()
  {
    init();
  }
  virtual void init()
  {
    printf("MyClass1 init");
  }
}
class MyClass2 : public MyClass1
{
public:
  MyClass2()
    : MyClass1()
  {
  }
  virtual void init()
  {
    printf("MyClass2 init");
  }
}

我希望 MyClass2 覆盖 MyClass1 初始化方法但它仍然显示"MyClass1 init"

C++如何像java/C#覆盖方法一样工作?

=== 更新2 ===

class MyClass1
{
public:
  MyClass1()
  {
    init();   <--- can't work like test method ??? 
  }
  void test()
  {
    init();   <--- work fine
  }
  virtual void init()
  {
    printf("MyClass1 init");
  }
}
class MyClass2 : public MyClass1
{
public:
  MyClass2()
    : MyClass1()
  {
  }
  virtual void init()
  {
    printf("MyClass2 init");
  }
}

我知道obj->init()会调用MyClass2::init。
但我希望C++可以在构造函数方法中运行。

虽然obj->init()可以解决。
但我希望代码可以少写一点。
有些人忘记调用 init()。

Java/C#可以少写多做更多的事情。但C++不能....这是非常令人沮丧的。

显然在构造MyClass2的过程中,MyClass1的构造函数将首先调用,即派生类对象的基类部分是在派生类部件之前构造的。即使你明确地尝试创建MyClass2对象,但在基类构造期间,虚函数永远不会进入派生类。

由于基类构造函数在派生类构造函数

之前执行,因此在基类构造函数运行时尚未初始化派生类数据成员。如果在基类构造期间调用的虚函数下降到派生类,则派生类函数几乎肯定会引用本地数据成员,但这些数据成员尚未初始化,这将导致未定义的行为。

参考你的班级,你为什么首先有virtual init?构造函数应分别完成工作。

如何从基类构造函数调用派生类虚拟方法?

这是个坏主意,永远不应该这样做!

class MyClass1
{
public:
  virtual ~MyClass1() { }
  virtual void doSomething()
  {
    printf("MyClass1 init");
  }
};
class MyClass2 : public MyClass1
{
public:
  virtual void doSomething()
  {
    printf("MyClass2 init");
  }
};
int main()
{
  MyClass1*obj = new MyClass2();
  obj->doSomething();
  delete obj;
  return 0;
}

一般来说,你不能。这是因为 MyClass2 构造函数尚未运行,因此如果要调用MyClass2::init(),则任何成员变量都将未初始化。在此构造函数启动之前,出于虚拟目的,对象被视为MyClass1实例。

例如:

class MyClass2 : public MyClass1
{
private:
    std::string xxx;
public:
    virtual void init()
    {
        xxx = "ops!"; //undefined if called from base class constructor
    }
};

xxx赋值将呈现未定义的行为,因为其构造函数尚未运行。

鉴于你缺乏结尾;我猜你在 Java/C# 方面的背景。这些语言以不同的方式实现了这个问题,但这就是C++的工作方式。

我不确定这是否合法或将来可能会破裂。但是,使用 lambda 函数,您可以将代码发送到基类以在构造函数期间执行。

class Base {
public:
    Base(std::function<void()> fnCall=nullptr) {
        if (fnCall) fnCall();
    }
    Base(const Base &base, std::function<void()> fnCall = nullptr) {
        DoSomethingAtBaseHere();
        if (fnCall) fnCall();
    }
};
class Derived: public Base {
public:
    Derived(std::function<void()> fnCall=nullptr) : Base([&]() {
        DoSomethingAtDerivedHere();
        if (fnCall) fnCall();
    }) {}
    Derived(const Derived &oth, std::function<void()> fnCall=nullptr) : 
    Base(oth, [&]() {
        DoSomethingInCopyConstructorAtDerivedHere();
        if (fnCall) fnCall();
    })  {}
};

我认为:
没有理由C++不能做java/C#虚拟构造器机制。
我想打破C++规则。
我也希望C++能少写多做事。
如果C++不提高,就不会进步,让更多的人无法轻易进入。

class MyClass1
{
public:
  MyClass1()
  {
    init();
  }
  typedef void (MyClass1::virtual_init)();
  MyClass1(virtual_init vinit)
  {
    (this->*vinit)();
  }
  //virtual void init()
  void init()
  {
    printf("MyClass1 init");
  }
}
class MyClass2 : public MyClass1
{
public:
  MyClass2()
    : MyClass1(&MyClass2::init)
  {
  }
  //virtual void init()
  void init()
  {
    printf("MyClass2 init");
  }
}

我遇到了同样的问题,我使用了CRTP(奇怪的重复模板模式)来解决这个问题:

#include <stdio.h>
#define derived static_cast<T*>(this)
template<class T> // CRTP
class MyClass1
{
public:
  MyClass1()
  {
    derived->init();
  }
  void init()
  {
    printf("MyClass1 initn");
  }
};
class MyClass2 : public MyClass1<MyClass2>
{
public:
  void init()
  {
    printf("MyClass2 initn");
  }
};
int main()
{
  MyClass2 *obj2 = new MyClass2();
  return 0;
}

此程序按预期打印MyClass2 init,而不是MyClass init。如果派生类中的函数不存在,则调用父类函数。

CRTP 的优点:

  • 静态编译的函数调用
  • 比虚拟功能更快
  • 可以控制调用哪个方法

CRTP 的缺点:

  • 可读性稍差
  • 父类派生类调用的所有函数都必须public。相应的父类函数也必须是公共的。
  • 需要将类名作为模板参数传递