无法调用C++中的派生类函数

Unable to invoke derived class function in C++

本文关键字:派生 类函数 C++ 调用      更新时间:2023-10-16

我肯定错过了代码的某些部分。

我有以下代码:

#include <iostream>
using namespace std;
class Record
{
private:
    int age;
    string name;
public:
    virtual int getType()=0;
};

class Student:  public Record
{
  private:
     int level_;
  public:
     Student()
     {
        level_=1;
     };
     ~Student() {};
     int getType()
     {
        return 1;
     }
     int level()
     {
        return level_;
     }
};

int main (int argc, char ** argv)
{
  Record *r = new Student();
  cout <<"tuype " << r->getType();
  cout <<"Class " << r->level();
}

问题是:为什么我不能调用r->level()?。需要进行哪些更改才能调用它?

更改记录以使level()虚拟

您写道:

Record *r = new Student();

在这一行之后,编译器认为r是指向Record或某个Record派生类(它就是这样)的指针,但它只知道为Record指定的接口。Record中没有virtual level()函数,因此无法通过Record接口访问Student的级别函数。只要在Record中添加这样一个函数,您就可以了:

virtual int level() { return 0; }  // Student may override implementation

virtual int level() = 0;   // Student MUST override implementation

另一种选择:检查记录*是否针对学生

我在上面说。。。

Record中没有virtual level()函数,因此无法通过Record接口访问Student的级别函数。

并显示如何添加到Record接口,但另一种选择是再次访问Student接口,如:

if (Student* p = dynamic_cast<Student*>(r))
    std::cout << "Level " << p->level() << 'n';

第一行检查Record* r是否恰好指向Student(当然,在您的代码中总是这样,但假设您在一个接受Record*的函数中,或者在这样的指针容器上循环,其中一些指针实际上是Student,而另一些则不是)。如果是这样的话,返回的指针可以作为Student对象来访问它,并且可以使用任何额外的功能/成员(如果某些Record功能以某种方式隐藏,则可能存在限制)。

这种方法通常不受欢迎,因为它引出了一个问题:"如果我们需要知道"级别",而其他Record派生的类型甚至没有级别的概念,为什么我们要将Student视为Record?"。尽管如此,类似的事情有时也会发生。如果Student是唯一一个具有有意义值的派生类,那么在Record中添加virtual level函数也不理想:这就是所谓的胖接口——如果你有副本,你会在C++编程语言中找到一些关于它们的讨论。

(sasha.sochka的回答是第一个提到dynamic_cast选项的——请投赞成票)

基类应该具有虚拟析构函数

根据chris的评论,您应该添加到Record:

virtual ~Record() { }

这确保了当派生对象是使用基类指针的deleted时(例如,如果在main()的底部添加delete r;)调用派生类的析构函数实现。(编译器仍然会确保基类析构函数在之后被调用)。

如果你不这样做,那就是未定义的行为,最多你会发现在派生类中添加的任何其他数据成员都没有调用它们的数据成员。。。对于int来说,这是无害的,但对于std::string来说,它可能会泄漏内存,甚至持有锁,使程序稍后挂起。当然,依赖最佳情况下的未定义行为不是一个好主意;-)但我认为,除非你制作了基础析构函数virtual,否则了解肯定不会发生什么是有用的。

学生版的推荐小改进

如果您在Record中设置level virtual,那么为了让Student类的读者更清楚地了解level()是基类中virtual函数的实现,如果您有一个适当的支持C++11的编译器,则可以使用override关键字:

int level() override
{
    return level_;
}

如果在基类中找不到匹配的(非constvirtual int level(),这将导致编译器错误,因此可以避免一些偶尔的故障排除。如果您觉得virtual关键字有文档价值,您也可以重复它(对于C++03来说尤其好,因为override不是一个选项),但它在功能上没有任何区别——只要它(隐式或显式)覆盖基类中的虚拟函数,该函数就会保持virtual

您无法调用r->level(),因为您正试图调用Record类中不存在的函数。在您的特定情况下,r指向的数据不仅是Record,而且同时也是Student,因此您可以选择:

Student *r = new Student();
cout <<"tuype " << r->getType();
cout <<"Class " << r->level()

Record *r = new Student();
cout <<"tuype " << r->getType();
cout <<"Class " << dynamic_cast<Student*>(r)->level()

如果您希望所有Record都有一个级别,那么您可以添加纯虚拟函数而无需实现。但如果你这样做,你将无法创建类Record的实例化对象(但你可以初始化它的子类):

class Record { ...
   virtual int level() = 0;
}

另一个问题是:您应该将Record类中的析构函数标记为虚拟的,因为当您使用Studentdelete r构造函数时,它将不会被调用

因为Record类对名为level()的函数一无所知

您需要在基类中创建一个虚拟level()函数。

在基类中生成类似的虚拟函数

virtual int level() = 0;

一旦你在基类Record中创建了虚拟函数level(),学生就有必要在学生类中拥有它的函数level()。目前,Record类中没有虚拟level()函数,因此,您无法像当前那样使用基类Record访问Student类的level()函数。

您在Record中缺少虚拟方法级别()声明。顺便说一句,为了防止资源泄漏,您需要在Record中定义虚拟析构函数。

class Record
{
...
public:
  virtual ~Record() {}
  virtual int level() = 0;
  virtual int getType() = 0;
};

学生实例由上传到Record

Record *r = new Student();

当持有学生实例时,代表Record。r->getType()函数调用绑定到带有C++多态性机制的student::getType。要调用level()函数,您可以:

1.向Record类添加一个虚拟函数级别()。2.将r向下转换为Student类,如下所示:
Student *r_new = dynamic_cast<Student>(r); 
r_new->level();

编译时,应该会收到类似于以下内容的错误消息:

error C2039: 'level' : is not a member of 'Record'

你需要在你的记录类中添加这个:

virtual int level()=0;