代码之家  ›  专栏  ›  技术社区  ›  davenpcj

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

  •  1
  • davenpcj  · 技术社区  · 6 年前

    更新: 此问题是由内存使用不当引起的,请参阅 解决方案 在底部。

    下面是一些半伪代码:

    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;
    }
    

    以下错误在运行时发生,尽管已取消驱动的函数已被声明和定义。

    pure virtual method called
    terminate called without an active exception
    

    注: 似乎错误可能是由内存使用不当引起的。详情请参见自答。

    更新1 - 4:

    注释已删除(不相关)。

    解决方案:

    Posted as an answer.

    8 回复  |  直到 6 年前
        1
  •  5
  •   David Rodríguez - dribeas    16 年前
    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
    }
    

    如果在基类中不希望实现函数,则必须声明为:

    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();
    }
    

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

        2
  •  4
  •   Chris Dodd    16 年前

    如果你得到的是 在没有活动异常“错误消息的情况下终止调用,这意味着您正在从ClassA(基类)的构造函数或析构函数调用虚拟函数,而您不应该这样做。

        3
  •  3
  •   David Rodríguez - dribeas    16 年前

    在称为错误的纯虚拟方法上:

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

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

    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
    }
    

    上面代码中的问题是编译器将允许您实例化派生类型的对象(因为它不是抽象的:所有虚拟方法都是实现的)。一个类的构造从所有基础的构造开始,在本例中是基础。编译器将为类型base生成虚拟方法表,其中 F-() 为0(未在基中实现)。然后编译器将在构造函数中执行代码。在基本部分完全构建之后,派生元素部分的构造就开始了。编译器将更改虚拟表,以便 F-() 指向 导出::() .

    如果你尝试调用这个方法 F-() 在仍然构造基的同时,虚拟方法表中的条目仍然为空,应用程序崩溃。

        4
  •  1
  •   Jason Coyne    16 年前

    当a调用virtualFunction()时,它将自动调用b上的版本,这就是虚函数的意义所在。

    我不太熟悉C++语法THO。您是否必须声明函数在主体点和头部都是虚拟的?

    alsop,在B类中,您可能需要将其标记为override

    在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
        }
    }
    
        5
  •  0
  •   To1ne    16 年前

    如果要强制派生类实现 VirtualFunction :

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

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

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

    void ClassA::SomeFunction()
    {
        // ... various lines of code ...
    
         ClassA::VirtualFunction();
    }
    
        6
  •  0
  •   Troubadour    16 年前

    您的代码没有问题,但您的示例不完整。您不会声明从何处调用某个函数。

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

    编辑:我回复的以下段落不正确。道歉。可以从ClassB的构造函数调用一些函数,因为vTable(至少)在初始化器列表的末尾已经就位(即,一旦您进入了构造函数的主体中)。当然,从ClassA的构造函数调用它是不好的。

    原始段落:

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

        7
  •  0
  •   Thierry Roy    16 年前

    要调用virutal函数,需要通过指针或引用调用。

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

    您需要能够区分这两种不同类型的调用,否则当类a::virtualFunction()被重写时,它将变得不可访问。

    正如其他人指出的那样,如果您想使基类版本抽象化,那么使用=0而不是

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

    但有时空定义是合法的。这取决于你的确切用法。

        8
  •  0
  •   casperOne    16 年前

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

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

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