【问题标题】:How to call derived class virtual method from base class constructor?如何从基类构造函数调用派生类虚方法?
【发布时间】:2013-02-02 09:07:09
【问题描述】:
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++ 可以在 Constructor 方法中运行。

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

Java/C# 可以少写多做。但是 C++ 不能……这很令人沮丧。

【问题讨论】:

  • 从基类构造函数调用虚函数并不是一个非常明智的选择。为什么不能直接从派生的构造函数中调用派生的init
  • 做你想做的事有点违背多态性的目的。顺便说一句,MyClass2 的构造函数无论如何都会被调用。
  • 你不能。派生类尚不存在,无法调用。
  • 使用Qt,您可以声明函数Q_INVOKABLE,并在构造函数完成时执行QMetaObject::invokeMethod 调用它

标签: c++ c++11


【解决方案1】:

显然在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;
}

【讨论】:

  • 你将基函数标记为虚拟,在派生类中覆盖它。那么当你调用:MyClass1 *obj = new MyClass2(); obj-&gt;init();,MyClass2::init 将被调用
  • 我知道 obj->init() 会调用 MyClass2::init。但我希望 C++ 可以在 Constructor 方法中运行。
  • C++ 不能那样工作。你为什么需要它?那是指设计一个合适的 C++ 应用程序。
  • 虽然obj->init()可以解决。但是希望代码可以少写一点。有些人忘记调用 init()。
  • 是的,obj-&gt;init() 可以,但不是来自基本构造函数!
【解决方案2】:

一般来说,你不能。那是因为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++ 的工作原理。

【讨论】:

  • 但我希望 C++ 可以覆盖方法,C++ 如何像 java/C# 覆盖方法一样工作?
  • @Flash:当然可以。不同之处在于,在 C++ 中,如果您从基类的构造函数调用虚方法,则调用 不会 被覆盖。一旦对象被完全构造,虚拟方法将按预期工作。它的工作原理是保护您免受我的示例代码中的错误的影响。
  • @Flash:这些规则的存在是有原因的。如果您不喜欢 C++,请务必不要使用它。但如果你使用它,那么你必须遵守规则。它们与您最喜欢的语言不同的事实并没有使它们“更少”,而只是“不同”。
【解决方案3】:

我不确定这是否合法或将来可能会中断。但是,使用 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();
    })  {}
};

【讨论】:

    【解决方案4】:

    我认为:
    没有理由 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");
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-09-27
      • 2011-03-05
      • 2019-04-19
      • 1970-01-01
      • 2016-07-19
      • 2011-05-03
      • 1970-01-01
      • 2018-07-21
      • 1970-01-01
      相关资源
      最近更新 更多