在虚函数周围使用#ifdef 预处理器会导致链接到库的程序出现运行时错误

Using #ifdef preprocessor around virtual functions causes runtime error in program linked against libraries

我试图围绕虚函数使用#ifdef 预处理器。代码的简化版本如下所示:

class Base
{
#ifdef ENABLE_FLAG
    virtual void function1();
#endif //ENABLE_FLAG

    virtual void function2();
    virtual void function3();
};

class Child : public Base
{
#ifdef  ENABLE_FLAG
    void function1() override;
#endif //ENABLE_FLAG

    void function2() override;
    void function3() override;
};

代码编译正常。但是,当我的应用程序调用 Child::function3() 时,它实际上由于某种原因最终调用了 Child::function2()。我认为预处理器以某种方式搞砸了虚拟 table。

我在 visual studio 2017 年处于 运行 调试模式。我很好奇这个运行时问题的原因是什么。这是依赖于编译器的行为吗?

另一个需要注意的有趣的事情是,如果我确保 ENABLE_FLAG 已定义并删除子 class 中的 #ifdef 子句并保留基 class 中的子句,则编译器实际上抛出一个编译错误。这有什么区别?

更新:此 class 在主程序和库中均有使用。

虚函数列在每个 class table - virtual function tablevtable - class 的每个实例都包含指向这个 table 本身包含指向 class 的每个虚函数的指针, 按照它们在 class 声明 中列出的顺序排列(*). (如果你想看的话,有很多描述虚拟 table 的教程文章和视频。)

因此,如果您在启用 #ifdef 的情况下编译程序的 部分 ,并使用 部分 #ifdef 启用-不同部分看到的虚函数数量会有所不同,vtable会有所不同。这是不应该发生的,并会导致您遇到的问题。

所以不要那样做。你违反了一个非常严格的 C++ 规则,称为 ODR"One Definition Rule"。如果你这样做,一切都悬而未决——也就是说,任何事情都可能出错。

(关于 ODR 的奇怪之处:对于如此严格的关键规则,编译器(即整个编译系统)不需要以任何方式告诉您您违反了它。所以,真的, 不要.

顺便说一句,所有库和主程序必须看到相同的 class 声明!上面关于 ODR 的内容? C++ 标准不知道“库”——静态的、动态的或其他。对于它只有程序和编译单元。 (这些“库”的东西只是编译系统为我们提供的一种便利。)所以所有的规则都适用于整个程序——而 ODR 是最常在这里咬你的! (正如您刚刚发现的...)

(*) 初步近似,不包括多重继承 ...