C++函数覆盖的思考
最近碰到一些问题,一开始很难调试和解决,最后发现原来是在基类函数的模板方法中对子类需要重写的函数没有使用virtual,如下
class Base
{
public:
void say(){test();}
void test(){}
}; class Child : public Base
{
public:
void test(){}
};
准备用Template Method的我很有自信的敲着自己的代码,期待在
Child a;
a.say();
的时候,a会去最终调用Child的test。注意,这里面没有牵扯到任何基类指针乃至基类引用指向子类的概念,纯粹是一个Child实体,我一厢情愿的以为a的say在进入Base的say之后,会去调用Child的test.因为它本身就是Child嘛~但就是这么个很小的细节,让我的问题在出现的时候我很难理解。真实的情况是此时的test实际调用的是Base的test。但如果我们换个写法
class Base
{
public:
void say(){test();}
virtual void test(){}
}; class Child : public Base
{
public:
virtual void test(){}
};
此时a.test会去最终调用Child的test。为什么,这里面根本就没有基类指针或者基类引用去指向子类的过程啊!虚函数的实现不是一定要通过指针或者引用吗?纯粹这样的实体类为什么也会采取动态绑定?
其实这也只能怪自己读书太肤浅,一直天真的以为动态绑定的实现必须满足两个先天条件。即virtual关键字的满足和基类指针和引用的满足。其实我们错了,C++中对应virtual机制来说,不过你是不是指针或者引用,只要你是Child实体并且满足virtual重写父类,那么无论如何,都会进入到你自己的函数当中去。因此,当我们要实现设计模式中模板方法模式的时候,不要怀疑和犹豫我此时需要子类实现的函数要不要虚函数,因为答案一定是一定要。你可以考虑这样的情况,其实只有两种方法会真正用到子类,一种是基类指针或者引用,一种就是子类实体本身,结合上文案例,不管这两种情况你采用哪种,当对应到test进行选择的时候,如果你用virtual都会根据最终的实际类型去选择,这就是区别。
再比如,当我们要抽象A与B提炼公共类C的时候,其实不需要考虑到底外部会不会对C进行调用,就算没有对C进行引用,一旦你做了公共分离,你就会在公共代码中去调用你子类的函数,此时你的公共类中必然有这个函数的空定义或者接口,你可以考虑如果不进行virtual修饰那情况会是什么样。比如
class C
{
public:
void say() {test();}
void test();
}; class A
{
public:
void test();
}; class B
{
public:
void test();
};
你将公共代码的say进行提炼到父类C的时候,当外界对你的A进行a.say(),自然而然会去调用C的say,但这个时候,里面的test要注意不是virtual,为什么会出现这种选择?因为我们在做抽象分离的时候确实会去考虑这个问题----倘若外界没有对我公共接口的访问,我还需要虚函数吗?
答案很明显已经出来了,结合上文,即使你外界没有对你的C进行任何接口调用,你的A或者B都是写死的,比如A a; B c;没有 C *a = new A等,你也务必需要在提取公共类的时候显示加上virtual
class C
{
public:
void say() {test();}
virtual void test();
}; class A
{
public:
virtual void test();
}; class B
{
public:
virtual void test();
};
因为只有这样,外界对A进行say访问的时候,最终的test才会调到它自己,否则,就会去调C的test。
总结,归根结底,C++的virtual并不是说一定要和基类指针或者引用挂钩才会起作用(其实很多介绍多态的书在举例子的时候大多采用这种写法因此会给我们带来一定误导,让我们始终认为这两个条件的必要性),其实哪怕只是实例,也是需要将virtual考虑在内的。
C++函数覆盖的思考的更多相关文章
- C++中函数重载和函数覆盖的区别
C++中经常会用到函数的重载和覆盖,二者也在很多场合都拿出来进行比较,这里我就对二者的区别做点总结: 函数重载: 函数重载指的是函数名相同.函数特征值不同的一些函数,这里函数的特征值指的是函数的参数的 ...
- C++ ------ 虚函数覆盖、重载
在C++语言中,虚函数是非常重要的概念,虚函数是实现C++面向对象中多态性和继承性的基石.而多态性和继承性则是面向对象语言的精髓.掌握虚函数才算是真正掌握C++语言,而C++语言中虚函数的继承覆盖与函 ...
- 关于JavaScript预编译和执行顺序以及函数引用类型的思考
昨晚在对项目中的一部分做模块化处理的时候,遇到了一个问题,一个重新定义的function对一个通用类中的function进行赋值覆盖的时候,失败了.问题抽象出来是这样的: <script > ...
- 由项目中一个hash2int函数引发的思考
hash2int /** * 计算一个字符串的md5折算成int返回 * @param type $str * @return type */ function hash2int($str) { $m ...
- 关于Linux系统basename函数缺陷的思考
某模块作为前台进程独立运行时,运行命令携带命令行参数:作为某平台下守护进程子进程运行时,需要将命令行参数固化在代码里.类似如下写法: char *argv[] = {"./DslDriver ...
- java中的异常处理机制_函数覆盖时的异常特点
/*注意:异常声明在函数上 异常在子父类覆盖时的体现1.子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或者异常的子类2.如果父类方法抛出多个异常,那么子类在覆盖该方法 ...
- C++多态实现(虚函数,成员函数覆盖、隐藏)
// 1.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> using namespace ...
- C# 函数覆盖总结学习
覆盖类成员:通过new关键字修饰虚函数表示覆盖该虚函数.一个虚函数被覆盖后,任何父类变量都不能访问该虚函数的具体实现.public virtual void IntroduceMyself(){... ...
- linux 动态库 静态库 函数覆盖
本文讨论了linux动态库 静态库中函数的覆盖问题. 测试目的: 同名函数,分别打成动态库libdync_lib.so与静态库libstatic_lib.a,并把libstatic_lib.a打到另 ...
随机推荐
- jQuery Validate 验证,校验规则写在控件中的具体例子
将校验规则写到控件中 <script src="../js/jquery.js" type="text/javascript"></scrip ...
- module_init和init_module的区别
今天在看CS8900的驱动时,发现其驱动的模块加载函数是init_module(),由于看到大多数的驱动用的模块加载函数大多是module_init()函数,所以一时没缓过神来,总是在找CS8900的 ...
- 字符串(后缀自动机):NOI 2016 优秀的拆分
[问题描述] 如果一个字符串可以被拆分为 AABB 的形式,其中 A 和 B 是任意非空字符串, 则我们称该字符串的这种拆分是优秀的. 例如,对于字符串 aabaabaa,如果令 A = aab, B ...
- poj1743 Musical Theme(后缀数组|后缀自动机)
[题目链接] http://poj.org/problem?id=1743 [题意] 求不可重叠最长重复子串. 2015-11-27 [思路] 1) 据题意处理字符串 ...
- zoj 2404 最小费用流
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2404 #include <cstdio> #incl ...
- Timeout for tests
如果想把timeout太久的测试自动标记为失败,有两种方法: 1.在 @Test里加上 Timeout 参数 定义"timeout=1000“的话,如果超过1000 毫秒,failure会被 ...
- ServletConfig
ServletConfig Servlet配置 比如web程序中的某一个Servlet需要配置一些初始化信息,需要在web.xml中进行配置 <servlet> <servlet-n ...
- Solr和ES对比
Solr与ES(ElasticSearch)对比 搜索引擎选择: Elasticsearch与Solr 搜索引擎选型调研文档 Elasticsearch简介* Elasticsearch是一个实时的分 ...
- 在树莓派1B上编译安装lnmp服务器
最近一周给部门内部搭建考试系统,选择使用PHPEMS.这是个开源的系统,唯一缺点是PHP的版本比较低,只能使用5.2或5.3.而我的树莓派系统更新后使用apt-get安装得到的PHP版本为5.4.由于 ...
- android——wifi系统架构
1. 系统架构 Android WiFi系统引入了wpa_supplicant,它的整个WiFi系统以wpa_supplicant为核心来定义上层用户接口和下层驱动接口.整个WiFi系统架构如下图所示 ...