为什么必须定义未使用的虚函数?
Why must unused virtual functions be defined?
与未使用的普通函数不同,未使用的虚函数仍必须定义,我觉得很奇怪。我对在创建 class 对象时创建的隐式 vtables 和 vpointers 有所了解 - 这在某种程度上回答了问题(即必须定义该函数,以便可以定义指向虚函数的指针)但这将我的查询推得更远。
如果根本不可能调用虚函数,为什么还需要为函数创建 vtable 条目?
class A{
virtual bool test() const;
};
int main(){
A a; //error: undefined reference to 'vtable for A'
}
即使我声明了 A::test()
它从未在程序中使用过,但它仍然会抛出错误。编译器是否可以不通过程序 运行 并意识到 test()
从未被调用 - 因此不需要 vtable 条目?还是对编译器的期望是不合理的?
因为对于编译器编写者而言,这不可避免地是一个非常难以解决的问题,当能够保留未定义虚函数的有用性充其量是值得怀疑的时候。编译器作者肯定有更好的问题需要解决。
此外,您正在使用该函数,即使您没有调用它。您正在使用它的地址。
OP说他已经知道vtables和vpointers,所以他明白未使用的虚函数和未使用的非虚函数之间的区别:未使用的非虚函数在任何地方都没有被引用,而虚函数函数在其 class 的 vtable 中至少被引用一次。所以,本质上,问题是问为什么编译器不够聪明,如果某个函数没有在任何地方使用,就不会在 vtable 中放置对虚函数的引用。这将允许该函数也未定义。
编译器通常一次只能看到一个 .cpp 文件,因此它不知道您是否在某处有一些源文件调用该函数。
一些工具支持这种分析,他们称之为"global"分析或类似的东西。您甚至可能会发现它内置在某些编译器中,并且可以通过某些编译器选项访问。但默认情况下它永远不会启用,因为它会极大地减慢编译速度。
事实上,非虚函数之所以可以不定义,也和没有进行全局分析有关,只是换个方式:如果编译器知道你省略了a的定义功能,它可能至少会警告你。但由于它不进行全局分析,所以不能。如果您 do 尝试使用未定义的函数,编译器将不会捕获错误:它会被链接器捕获。
所以,只需定义一个包含 ASSERT(FALSE)
的空虚函数,然后继续你的生活。
虚函数的全部意义在于它们可以通过基 class 指针调用。如果你从不使用 base class 虚函数,那么,你为什么要定义它?如果使用它,您要么必须离开父实现(如果它不是纯虚拟的),要么定义您自己的实现,以便通过基础 class 使用您的对象的代码实际上可以使用它。在那种情况下,函数是使用的,只是没有直接使用。
与未使用的普通函数不同,未使用的虚函数仍必须定义,我觉得很奇怪。我对在创建 class 对象时创建的隐式 vtables 和 vpointers 有所了解 - 这在某种程度上回答了问题(即必须定义该函数,以便可以定义指向虚函数的指针)但这将我的查询推得更远。
如果根本不可能调用虚函数,为什么还需要为函数创建 vtable 条目?
class A{
virtual bool test() const;
};
int main(){
A a; //error: undefined reference to 'vtable for A'
}
即使我声明了 A::test()
它从未在程序中使用过,但它仍然会抛出错误。编译器是否可以不通过程序 运行 并意识到 test()
从未被调用 - 因此不需要 vtable 条目?还是对编译器的期望是不合理的?
因为对于编译器编写者而言,这不可避免地是一个非常难以解决的问题,当能够保留未定义虚函数的有用性充其量是值得怀疑的时候。编译器作者肯定有更好的问题需要解决。
此外,您正在使用该函数,即使您没有调用它。您正在使用它的地址。
OP说他已经知道vtables和vpointers,所以他明白未使用的虚函数和未使用的非虚函数之间的区别:未使用的非虚函数在任何地方都没有被引用,而虚函数函数在其 class 的 vtable 中至少被引用一次。所以,本质上,问题是问为什么编译器不够聪明,如果某个函数没有在任何地方使用,就不会在 vtable 中放置对虚函数的引用。这将允许该函数也未定义。
编译器通常一次只能看到一个 .cpp 文件,因此它不知道您是否在某处有一些源文件调用该函数。
一些工具支持这种分析,他们称之为"global"分析或类似的东西。您甚至可能会发现它内置在某些编译器中,并且可以通过某些编译器选项访问。但默认情况下它永远不会启用,因为它会极大地减慢编译速度。
事实上,非虚函数之所以可以不定义,也和没有进行全局分析有关,只是换个方式:如果编译器知道你省略了a的定义功能,它可能至少会警告你。但由于它不进行全局分析,所以不能。如果您 do 尝试使用未定义的函数,编译器将不会捕获错误:它会被链接器捕获。
所以,只需定义一个包含 ASSERT(FALSE)
的空虚函数,然后继续你的生活。
虚函数的全部意义在于它们可以通过基 class 指针调用。如果你从不使用 base class 虚函数,那么,你为什么要定义它?如果使用它,您要么必须离开父实现(如果它不是纯虚拟的),要么定义您自己的实现,以便通过基础 class 使用您的对象的代码实际上可以使用它。在那种情况下,函数是使用的,只是没有直接使用。