更改记录以进行level()
虚拟化
你写了:
Record *r = new Student();
在该行之后,编译器认为r
是指向一个Record
或某个Record
派生类(它是)的指针,但它只知道为Record
. 中没有virtual
level()
功能Record
,所以无法通过界面访问Student
的关卡功能。Record
只需添加这样一个功能Record
,你会没事的:
virtual int level() { return 0; } // Student may override implementation
或者
virtual int level() = 0; // Student MUST override implementation
另一种选择:检查记录*是否针对学生
我上面说...
中没有virtual
level()
功能Record
,所以无法通过界面访问Student
的关卡功能。Record
...并展示如何添加到Record
界面,但另一种方法是再次访问Student
界面,如下所示:
if (Student* p = dynamic_cast<Student*>(r))
std::cout << "Level " << p->level() << '\n';
第一行检查是否Record* r
恰好指向 a Student
(当然在你的代码中它总是这样,但是想象你在一个接受 a 的函数中Record*
,或者正在循环一个包含这样的指针的容器,其中一些是真正Student
的 s 而另一些不是)。如果是这样,则返回的指针可用于将其作为Student
对象访问,并具有任何可用的额外功能/成员(如果某些Record
功能以某种方式隐藏,则可能存在限制)。
这种方法通常不受欢迎,因为它引出了一个问题“如果我们需要知道“级别”而其他派生类型甚至没有级别的概念,为什么我们将其Student
视为?”。不过,这样的事情有时会发生。添加一个函数也不理想,如果它是唯一的派生类(其中一个)它会具有有意义的值:这就是所谓的胖接口 - 你会在 C++ 中找到一些关于它们的讨论编程语言,如果你有副本。Record
Record
virtual
level
Record
Student
(sasha.sochka 的回答是第一个提到该dynamic_cast
选项的人 - 请投票)
基类应该有虚拟析构函数
根据克里斯的评论,您应该添加Record
:
virtual ~Record() { }
这可确保在派生对象为 d 时使用基类指针调用派生类的析构函数实现delete
(例如,如果delete r;
在 底部添加 a main()
)。(编译器仍将确保随后调用基类析构函数)。
如果你不这样做,这是未定义的行为,充其量你会发现派生类中添加的任何其他数据成员都没有调用它们的数据成员......因为int
这是无害的,但是说它std::string
可能会泄漏内存,甚至持有一个锁,以便程序稍后挂起。当然,依赖最佳情况的未定义行为并不是一个好主意;-) 但我认为除非您制作基本的析构函数,否则了解绝对不会发生的事情是有用的virtual
。
推荐的学生小改进
如果您 make level
virtual
in ,那么为了让作为基类函数实现的类Record
的读者更清楚,如果您有适当的支持 C++11 的编译器,则可以使用关键字:Student
level()
virtual
override
int level() override
{
return level_;
}
如果在基类中找不到匹配的(非const
) ,这会给你一个编译器错误virtual int level()
,因此它可以避免一些偶尔的故障排除。virtual
如果您认为关键字具有文档价值,您也可以重复该关键字(对于 C++03 来说尤其好,其中override
' 不是一个选项),但它不会产生任何功能差异 -virtual
只要它存在(隐式或显式),函数就会保持) 从基类重写虚函数。