以下代码段可以正常使用:
#include <iostream>
#include <string>
using namespace std;
class HelpInterface {
public:
void getHelp();
};
class Applicaiton : public HelpInterface {
public:
void getHelp() {
cout << "General help";
}
};
int main(void) {
Applicaiton applicaiton;
applicaiton.getHelp();
}
在HelpInterface类中使getHelp函数变为虚拟,我将收到链接器错误:
class HelpInterface {
public:
virtual void getHelp();
};
如果我对getHelp进行空实现,则下面的内容会再次起作用:
class HelpInterface {
public:
virtual void getHelp() {};
};
有人可以帮助我理解为什么虚拟引发链接器错误,除非我在基类中有getHelp的实现以及为什么没有实现的非虚函数工作正常?在此示例中,永远不会调用基函数。
答案 0 :(得分:3)
如果您希望基类方法是虚拟的并且不提供任何实现,则必须将其设置为零,如下所示:
class HelpInterface {
public:
virtual void getHelp() = 0;
};
这称为纯虚方法。它具有使您的类抽象化并强制其所有派生类提供该方法的实现的效果。因此,请注意,您将无法再创建基类的实例,因为它是抽象的。
答案 1 :(得分:1)
当方法在基类中但不是虚拟的时,如果您没有在指向基类型的指针/引用上显式调用该方法,则链接器实际上不会引用该方法的实现(在派生类的实例上)或基本类型的实例。
虚拟功能可以通过多种不同方式实现,并且是特定于实现的。其中最常见的是使用虚拟表。 (也称为虚方法表,虚函数表,虚拟调用表,调度表,vtable或vftable),我很确定你的编译器(VS2013)使用这种实现虚函数的方法。
虚拟表是虚拟成员函数的函数指针表。类实例将包含指向该类所属表的指针。
当您创建虚拟函数时,链接器会尝试将其放入该类型的虚拟表中。无论您是否调用它或实例化基类(也是特定于实现的细节)都无关紧要。
正如qexyn已经回答的那样,为了解决这个问题,你可以通过在虚函数声明之后添加= 0
来将该方法声明为纯虚方法。这告诉链接器将空指针放在该类的虚函数表中。您还可以将虚拟函数声明为纯虚拟,并提供实现。这会强制派生类实现该函数,但允许它们选择使用默认方法。
答案 2 :(得分:0)
原因是您的基类定义使用间接来获取Application
的实际函数。
这通常使用函数指针实现,但在任何情况下都可能存在不会覆盖基类实现的派生类。
虽然this isn't usually reproducible只是因为成员函数被隐式声明inline
,并且当实现可见时,优化编译器将完全这样做;内联函数。 The proof is in the optimization.
您想要的是确保您的基础中的每个派生类都实现getHelp()
。这是一种常见的习语,是一种核心语言特征。你想要一个“纯粹的虚拟功能”。这将使您的基类成为“抽象基类”,它实际上是面向对象的jibber-jabber中“interface”的同义词。
在C ++中执行此操作的方法是在所有成员函数限定符之后使用特定的成员函数特定语法(我在这里使用尾随返回类型导致我认为它们很漂亮):
class base{
public:
virtual auto func() -> void = 0;
};
这指定base
是一个抽象基类,具有纯虚函数func()
,所有从中派生的类都将实现。
在你的情况下,你会写:
class HelperInterface{
public:
virtual void getHelp() = 0; // probably want const qualifier
};
然后保持原样Application
。
你每天都学到一些东西,嗯?