【问题标题】:What happens when an asynchronous callback calls a virtual function, while base class constructor is not returned yet?当异步回调调用虚函数,而基类构造函数尚未返回时会发生什么?
【发布时间】:2014-06-10 00:47:57
【问题描述】:

我有以下场景:

class Caller{
public:
  Caller()               {...}
  void register(Base* b) {...}
  void callBase() { b->virt()}
};

class Base {
public:
  Base(Caller c)      { println("Base::Base()");  c.register(this); sleep(30); }
  virtual void virt() { println("Base::virt()"); }
};

class Derived : public Base {
public:
  Derived()           { println("Derived::Derived()"); }
  virtual void virt() { println("Derived::virt()"); }
};

我通常知道如果有人在派生类上调用 virt,则将调用 Derived::virt()。但是在这里,如果在父构造函数中 Base 处于休眠状态时调用了 callBase 函数,会调用哪个函数呢? Base::virt() 还是 Derived::virt()?

谢谢

【问题讨论】:

标签: c++ constructor callback virtual asynccallback


【解决方案1】:

它会调用Base::virt。在基类构造函数完成之前,将分派虚函数,就好像动态类型是 Base

(虽然从技术上讲,如果另一个线程在没有适当同步的情况下访问该对象,您将有未定义的行为。并且由于register 是关键字,因此程序将无法编译。)

【讨论】:

    【解决方案2】:

    根据C++ Lite FAQ 23.5,会调用Base::virt()

    无论如何,这真的不是你想做的事情——如果你的对象在没有正确初始化的情况下被另一个线程使用,你可能会遇到各种讨厌的竞争条件。例如,如果第二个线程调用virt恰好在设置 vtable 时会发生什么?谁来保证在对象构造过程中设置vtable是原子操作?

    您应该以这样一种方式设计您的代码,即您的对象在完全初始化之前不会被使用。

    【讨论】:

    • 你是对的。我认为崩溃的原因是竞争条件,当子构造函数尚未完成(尚未创建字段)时,回调调用子 virt() 函数并尝试使用字段。
    【解决方案3】:

    Base 的构造函数或析构函数中,该类几乎在所有方面都像Base
    这意味着使用任何被调用的虚函数的Base-实现。

    如果没有(这是可能的,因为Base 可能将其声明为纯虚拟,使Base 成为抽象类),您将得到Undefined Behavior,所以一切都会发生。
    这种情况下常见的行为包括因无效访问而崩溃、因错误而故意中止以及调用某些派生类覆盖。

    如果调用是并发的(多线程/信号),标准说你直接去UB

    Java、C# 等在这方面有完全不同的规则,因此请单独检查每种新语言。

    旁白:在 C++ 中使用 register 作为名称是不明智的。这是不允许的,因为它是一个已失效(即完全没有效果)的存储类关键字。

    【讨论】:

      猜你喜欢
      • 2021-01-04
      • 2011-05-20
      • 1970-01-01
      • 2013-01-28
      • 2019-04-19
      • 2011-09-27
      • 2015-07-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多