我认为虚拟功能的实现已经谈了很多。我的问题是纯虚函数呢?但它实施了吗?在虚拟表中,如何判断它是纯粹的还是非纯粹的?纯虚函数和虚函数与实现有什么区别?
答案 0 :(得分:4)
纯虚函数和非纯虚函数之间通常没有实现差异。如果定义了纯虚函数,它就像任何其他虚函数一样。如果未定义,则仅在显式调用时才会导致问题。
行为有两个主要差异,但通常对虚函数机制本身的实现没有影响。编译器不允许构造具有纯虚函数的类型的对象,这些虚函数在其继承层次结构中没有非纯的最终覆盖,并且任何尝试直接或间接地从纯虚函数进行虚拟调用对象的构造函数或析构函数会导致未定义的行为。
答案 1 :(得分:3)
virtual void foo()= 0;
纯虚函数仍然是虚函数,因此它将在vtable中,但编译器不需要实现它,并且将禁止实例化声明纯虚函数的基类。由于您仍然可以取消引用抽象基类类型的指针,因此虚拟表必须具有一个条目,用于多态和运行时绑定。
这有帮助吗?
答案 2 :(得分:2)
这不是答案,而是对this answer above
的评论的后续跟进C ++语言定义了构造期间虚拟调度机制的发生方式。在层次结构中实例化对象时,将调用基础构造函数(*)。此时,为基类初始化虚拟调度机制。在此阶段,将将虚函数分派给基本实现。使用虚拟调度机制调用非纯虚方法(没有明确的类限定)将调用基本实现。
在完成基本构造函数之后,虚拟分派机制(通常是vtable)将被重置为派生类型版本,并且从那里开始的任何动态调用都将调用方法的派生版本:
struct base {
virtual void non_pure() { std::cout << "base::non_pure" << std::endl; }
virtual void pure_not_implemented() = 0;
virtual void pure_implemented() = 0;
base() { // at this point the object is a ´base´
non_pure(); // base::non_pure
// pure_not_implemented(); // runtime error: pure virtual method called
pure_implemented(); // base::pure_implemented
// base::pure_not_implemented(); // link error
}
};
void base::pure_implemented() { std::cout << "base::pure_implemented" << std::endl; }
struct derived : base {
virtual void non_pure() { std::cout << "derived::non_pure" << std::endl; }
virtual void pure_not_implemented() { std::cout << "derived::pure_not_implemented" << std::endl; }
virtual void pure_implemented() { std::cout << "derived::pure_implemented" << std::endl;
derived() { // after the implicit call to the base class
// this is a ´derived´ object, now calls will
// get dispatched to derived:: implementations
non_pure(); // derived::non_pure
pure_not_implemented(); // derived::pure_not_implemented
pure_implemented(); // derived::pure_implemented
base::non_pure(); // base::non_pure
// base::pure_not_implemented() // link error
}
};
请注意,使用动态调度机制(通常是vtable)和使用完全限定名称调用特定方法之间存在差异。在编译时将允许通过完全限定调用未实现的纯虚方法,但在链接时将失败(链接器无法找到要调用的实现)。
这是语言设计中的一个重要决定。另一个选项(Java采用的那个)是在调用基类构造函数之前从一开始就将虚拟调度机制初始化为最派生类型。这种方法的问题在于,如果基本构造函数调用一个虚方法(在Java中都是),它将被调度到最派生的实现,因此将在一个尚未构造的对象中执行,可能会导致意外的结果:
public class Base
{
public Base() {
f();
}
public void f() {
System.out.println("Base.f");
}
}
public class Derived extends Base {
public final int constant;
public Derived() { constant = 5; }
public void f() {
System.out.println( "Derived.f() " + constant );
}
public static void main( String args[] ) {
Derived d = new Derived(); // prints Derived.f() 0
}
}
在Java版本中,动态调度机制从一开始就将对象视为类型Derived
。基础构造函数中对f()
的调用将动态调度到派生实现。在上面的例子中,即使变量被声明为final,因此值为5(在代码中显而易见),实际打印值为0,因为Derived
的构造函数尚未执行。
(*)这是过于简单化,但细节并没有真正影响论点。
答案 3 :(得分:0)
我不知道实际的实现,但一个很好的选择是在NULL
中将其实现为vtable
指针。换句话说,如果你有一个实现,vtable
中有一个有效的函数指针,如果它是纯虚函数,你有一个NULL
指针。
这是合乎逻辑的,我甚至认为 以这种方式实现: - )。