【问题标题】:Why must unused virtual functions be defined?为什么必须定义未使用的虚函数?
【发布时间】:2016-01-07 21:33:55
【问题描述】:

我觉得很奇怪,与未使用的普通函数不同,未使用的虚函数仍然必须定义。我对在创建类对象时创建的隐式 vtablesvpointers 有所了解 - 这在某种程度上回答了这个问题(必须定义函数以便指向可以定义虚函数),但这会使我的查询更进一步。

如果根本不可能调用虚函数,为什么还需要为函数创建 vtable 条目?

class A{
    virtual bool test() const;
};

int main(){
    A a; //error: undefined reference to 'vtable for A'
}

即使我声明了A::test(),它也从未在程序中使用过,但它仍然会引发错误。编译器可以不运行程序并实现test() 从未被调用 - 因此不需要 vtable 条目吗?或者这是对编译器的不合理期望?

【问题讨论】:

  • 你不能实例化一个虚拟类。但是创建一个 null 函数或仅返回其声明类型的常量的函数是微不足道的。

标签: c++ function virtual definition


【解决方案1】:

因为对于编译器编写者而言,这将不可避免地成为一个非常难以解决的问题,而能够使虚函数保持未定义的有用性充其量是值得怀疑的。编译器作者肯定有更好的问题需要解决。

此外,即使您没有调用它,您也正在使用该函数。您正在获取它的地址。

【讨论】:

  • 我什至会说这是不可能解决的:假设您有一个使用dlopen 打开的库,它为您提供了一个指向具有未定义虚函数的类的指针。编译器不可能捕捉到这一点,所以这将是一个运行时错误......
【解决方案2】:

OP 说他已经知道 vtables 和 vpointers,所以他明白未使用的虚函数和未使用的非虚函数之间存在区别:未使用的非虚函数不会在任何地方引用,而虚函数是在其类的 vtable 中至少引用一次。所以,本质上问题是问为什么编译器不够聪明,无法避免在 vtable 中放置对虚函数的引用,如果该函数没有在任何地方使用。这将允许该函数也未定义。

编译器通常一次只能看到一个 .cpp 文件,因此它不知道您是否有某个源文件在某处调用该函数。

一些工具支持这种分析,他们称之为“全局”分析或类似的东西。您甚至可能会发现它内置在某些编译器中,并且可以通过某些编译器选项访问。但它从不默认启用,因为它会极大地减慢编译速度。

事实上,你可以不定义一个非虚函数的原因也与缺乏全局分析有关,但方式不同:如果编译器知道你省略了一个函数的定义,它可能至少会警告你。但是由于它不进行全局分析,所以它不能。事实证明,如果您确实尝试使用未定义的函数,编译器不会捕获错误:它会被链接器捕获。

所以,只需定义一个包含ASSERT(FALSE) 的空虚函数,然后继续你的生活。

【讨论】:

  • @AlexD 好吧,OP 说他已经知道 vtables 和 vpointers,所以我觉得我不需要解决这个问题。但是你是对的,最好是陈述事情而不是暗示事情。所以,我修改了我的答案。
  • "编译器通常一次只能看到一个 .cpp 文件,因此它不知道您是否有某个源文件调用了该函数。"如果我有一个调用该函数的源文件,那么该源文件是否不会包含该类的标头(缺少虚拟定义)?如果没有在源文件中定义类,如何调用成员函数?
  • @AntiElephant 有一个difference between declaration and definition。头文件将包含函数的声明,使得任何一段代码调用该函数都是合法的。但是函数的定义是你提供实际函数体的地方,这通常在一个.cpp文件中完成。
  • 抱歉,您所说的“调用”函数是指定义它吗?
  • @AntiElephant 不,“invoke”是指“invoke”通常的意思,也就是“call”。
【解决方案3】:

虚函数的全部意义在于它们可以通过基类指针调用。如果你从不使用基类虚函数,那你为什么要定义它呢?如果使用它,您要么必须离开父实现(如果它不是纯虚拟的),要么定义您自己的实现,以便通过基类使用您的对象的代码实际上可以使用它。在那种情况下,函数使用了,只是没有被直接使用。

【讨论】:

    猜你喜欢
    • 2021-12-25
    • 2016-10-01
    • 2011-06-26
    • 2013-06-24
    • 1970-01-01
    • 2012-10-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多