[英]Unable to invoke derived class function in C++
我确定我错过了部分代码。
我有以下代码:
#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()
? 调用它需要进行哪些更改?
更改为Record以使level()
虚拟
你写了:
Record *r = new Student();
在该行之后,编译器认为r
是指向Record
或某个Record
derived的类(它是)的指针,但它只知道为Record
指定的接口。 Record
没有virtual
level()
函数,因此您无法通过Record
接口访问Student
的level函数。 只需将这样的功能添加到Record
,你就可以了:
virtual int level() { return 0; } // Student may override implementation
要么
virtual int level() = 0; // Student MUST override implementation
另一种方法:检查Record *是否为学生提供解决方案
我上面说...
Record
没有virtual
level()
函数,因此您无法通过Record
接口访问Student
的level函数。
...并显示如何添加到Record
界面,但另一种方法是再次访问Student
界面,如:
if (Student* p = dynamic_cast<Student*>(r))
std::cout << "Level " << p->level() << '\n';
第一行检查Record* r
是否恰好指向一个Student
(当然在你的代码中它总是如此,但想象你是在一个接受一个Record*
的函数里面,或者是在一个这样的指针的容器上循环,其中一些真的是Student
而其他人不是)。 如果是这样,返回的指针可用于将其作为Student
对象访问,并且可以使用任何额外的功能/成员(如果某些Record
功能以某种方式隐藏,则可能存在限制)。
这种方法通常不受欢迎,因为它引出了一个问题:“如果我们需要知道”水平“,我们为什么将Student
视为Record
其他Record
来源的类型甚至没有水平概念?”。 不过,有时会发生类似的事情。 如果Student
是(其中一个)唯一具有有意义值的派生类,那么向Record
添加virtual
level
函数并不理想:这就是所谓的胖界面 - 你会发现一些讨论如果您有副本,请使用C ++编程语言。
(sasha.sochka的回答是第一个提到dynamic_cast
选项 - 请upvote)
基类应该有虚拟析构函数
根据chris的评论,你应该添加到Record
:
virtual ~Record() { }
这确保了在使用基类指针delete
派生对象时调用派生类的析构函数实现(例如,如果在main()
的底部添加delete r;
)。 (编译器仍将确保之后调用基类析构函数)。
这是未定义的行为,如果你不这样做,并且充其量你会发现在派生类中添加的任何其他数据成员都没有调用它们的数据成员...对于一个无害的int
,但是对于std::string
它可能会泄漏内存,甚至可以保持锁定,以便程序稍后挂起。 当然,依靠最佳案例的未定义行为并不是一个好主意;-)但我认为除非你使基础析构函数virtual
否则理解什么是绝对不会发生是有用的。
推荐学生的小改进
如果你在Record
创建了virtual
level
,那么为了让Student
类的读者更清楚level()
是一个基类的virtual
函数的实现,你可以使用override
关键字,如果你有一个合适的C ++ 11有能力的编译器:
int level() override
{
return level_;
}
这会给你一个编译器错误,如果它不能找到匹配的(非const
) virtual int level()
基类,所以它能够避免一些偶然的故障排除。 如果您认为virtual
关键字具有文档值,也可以重复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
类中的析构函数标记为虚拟,因为当你delete r
,不会调用Student
构造函数
因为Record
类对于名为level()
的函数一无所知
您需要在基类中创建一个虚拟的level()
函数。
在基类中创建虚函数
virtual int level() = 0;
一旦在基类Record中创建虚函数level()
,学生就必须在学生班级中拥有它的功能level()
。 目前,在Record类中没有virtual level()
函数,因此您无法使用当前正在执行的基类Record访问Student类的level()
函数。
您缺少Record中的虚方法级别()声明。 另外,为防止资源泄漏,您需要在Record中定义虚拟析构函数。
class Record
{
...
public:
virtual ~Record() {}
virtual int level() = 0;
virtual int getType() = 0;
};
学生实例被上传到记录:
Record *r = new Student();
持有Student实例,r代表Record。 r-> getType()函数调用绑定到student :: getType,带有C ++的Polymorphism machanism。 要调用level()函数,您可以:
1. Add a virtual function level() to Record class. 2. downcasting r to Student class, as follows:
Student *r_new = dynamic_cast<Student>(r);
r_new->level();
编译时,您应该收到类似于以下内容的错误消息:
error C2039: 'level' : is not a member of 'Record'
您需要在Record类中添加它:
virtual int level()=0;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.