为什么我的虚函数调用会失败?

时间:2009-04-10 16:44:23

标签: c++ virtual-functions

  

更新:此问题是由内存使用率不佳引起的,请参见底部的解决方案

这是一些半伪代码:

class ClassA
{
public:
    virtual void VirtualFunction();
    void SomeFunction();
}

class ClassB : public ClassA
{
public:
    void VirtualFunction();
}

void ClassA::VirtualFunction()
{
    // Intentionally empty (code smell?).
}

void ClassA::SomeFunction()
{
    VirtualFunction();
}

void ClassB::VirtualFunction()
{
    // I'd like this to be called from ClassA::SomeFunction()
    std::cout << "Hello world!" << endl;
}

C#等价物如下:删除了C#示例,因为它与实际问题无关。

ClassB::VirtualFunction调用时,为什么不调用ClassA::SomeFunction函数?相反,ClassA::VirtualFunction被称为......

当我force implementation of the virtual function ClassA :: VirtualFunction时,像这样:

class ClassA
{
public:
    virtual void VirtualFunction() = 0;
    void SomeFunction();
}

class ClassB : public ClassA
{
public:
    void VirtualFunction();
}

void ClassA::SomeFunction()
{
    VirtualFunction();
}

void ClassB::VirtualFunction()
{
    // I'd like this to be called from ClassA::SomeFunction()
    std::cout << "Hello world!" << endl;
}

在运行时发生以下错误,尽管已经声明并定义了deriinately。

pure virtual method called
terminate called without an active exception

注意:看起来即使内存使用不良也可能导致错误。有关详细信息,请参阅自我答案。

更新1 - 4:

删除了评论(不是重新发布)。

解决方案:

Posted as an answer.

8 个答案:

答案 0 :(得分:5)

class Base {
public:
   virtual void f() { std::cout << "Base" << std::endl; }
   void call() { f(); }
};
class Derived : public Base {
public:
   virtual void f() { std::cout << "Derived" << std::endl; }
};
int main()
{
   Derived d;
   Base& b = d;
   b.call(); // prints Derived
}

如果在Base类中你不想实现该函数,则必须声明:

class Base {
public:
   virtual void f() = 0; // pure virtual method
   void call() { f(); }
};

编译器不允许你实例化类:

int main() {
   //Base b; // error b has a pure virtual method
   Derived d; // derive provides the implementation: ok
   Base & b=d; // ok, the object is Derived, the reference is Base
   b.call();
}

作为附注,注意不要从构造函数或析构函数中调用虚函数,因为您可能会得到意想不到的结果。

答案 1 :(得分:4)

如果您正在使用“纯虚拟方法” 在没有活动异常的情况下调用终止'错误消息,这意味着你从classA(基类)的构造函数或析构函数中调用虚函数,这是你不应该做的。

答案 2 :(得分:3)

在名为error的纯虚方法上:

你应该创建一个不同的问题,因为它实际上与另一个不同。这个问题的答案在我之前对你的初步问题的答案的最后一段:

不要从构造函数或析构函数中调用虚函数

class Base
{
public:
   Base() { f(); }
   virtual void f() = 0;
};
class Derived : public Base
{
public:
   virtual void f() {}
};
int main()
{
   Derived d; // crashes with pure virtual method called
}

上面代码中的问题是编译器将允许您实例化Derived类型的对象(因为它不是抽象的:所有虚拟方法都已实现)。一个班级的建设从所有基地的建设开始,在这种情况下是基地。编译器将为类型Base生成虚方法表,其中 f()的条目为0(未在base中实现)。然后编译器将在构造函数中执行代码。在完成构建基础部件之后,将开始构建派生元素部件。编译器将更改虚拟表,以便 f()的条目指向 Derived :: f()

如果在构建Base时尝试调用方法 f(),则虚方法表中的条目仍为null,应用程序崩溃。

答案 3 :(得分:1)

当A调用VirtualFunction()时,它会自动调用B上的版本。这是虚函数的重点。

我对C ++语法不熟悉。您是否必须在主体和标题中声明该函数是虚拟的?

Alsop,在B级你可能需要将其标记为覆盖

在C#中很容易。我只是不知道c ++语法。

public class ClassA
{
    public **virtual** void VirtualFunction(){}

    public void FooBar()
    {
        // Will call ClassB.VirtualFunction()
        VirtualFunction();
    } 

}

public class ClassB
{
    public **overide** void VirtualFunction()
    {
        // hello world
    }
}

答案 4 :(得分:0)

您没有正确定义ClassB中的函数,它应该是:

public class ClassB
{
    public void override AbstractFunction()
    {
        // hello world
    }
}

然后,从基类到虚拟/抽象方法的任何调用都将调用派生实例上的实现。

答案 5 :(得分:0)

如果要强制派生类实现VirtualFunction

class ClassA
{
public:
    virtual void VirtualFunction()=0;
    void SomeFunction();
}

这是C ++。默认情况下将调用派生函数。

如果要调用基类函数,请执行以下操作:

void ClassA::SomeFunction()
{
    // ... various lines of code ...

     ClassA::VirtualFunction();
}

答案 6 :(得分:0)

您的代码没有任何问题,但您的示例不完整。你没有说明你从哪里调用SomeFunction。

正如dribeas已经指出的那样,你必须小心从构造函数中调用虚函数,因为虚拟表只是在层次结构中的每个类完成构造时构建的。

编辑:我的回复的以下段落不正确。道歉。可以从ClassB的构造函数中调用SomeFunction,因为vtable在初始化列表的末尾(至少)就位,即一旦你在构造函数的主体中。当然,从ClassA的构造函数中调用它是不对的。

原始段落:

我怀疑你必须从ClassB的构造函数中调用SomeFunction,此时只有类型为ClassA的vtable才能完成,即对于虚拟调度机制,你的类仍然是ClassA类型。它只在构造函数完成时成为ClassB类型的对象。

答案 7 :(得分:0)

要调用虚拟功能函数,您需要通过指针或引用进行调用。

void ClassA::SomeFunction()
{
    VirtualFunction();       // Call ClassA::VirtualFunction

    this->VirtualFunction(); // Call Via the virtual dispatch mechanism
                             // So in this case call ClassB::VirtualFunction
}

您需要能够区分两种不同类型的调用,否则当覆盖它时,classA :: VirtualFunction()将变得不可访问。

正如其他人所指出的,如果你想让基类版本抽象,那么使用= 0而不是{}

class A
{
    virtual void VirtualFunction() =0;
....

但有时候有一个空的定义是合法的。这取决于您的确切用法。