文档章节

再论C++的虚函数

笨笨_蛋蛋
 笨笨_蛋蛋
发布于 2015/07/28 12:38
字数 1254
阅读 246
收藏 1

最近正在用智能指针二次封装TT的netlib库,使其彻底面向对象(netlib是c风格的)并且对外具有更好的一致性和更易于使用。框架已经整好了,不过我还得用此新框架重写几个TT的服务器并且自测没有问题才能拿出来分享。总不能整个一堆问题的垃圾给大家吧。

对TT还不了解的朋友欢迎阅读我的前一篇博文 TeamTalk消息服务器原理及二次开发简介

也可以去读一读蓝狐大神的博客,具体网页自己搜了。

在用C++做面向对象的编程过程中,突然想到一个问题,那就是c++里的virtual,当然这里只讨论virtual函数,至于虚继承还有其他什么乱七八糟的(c++实在太复杂了)不谈。

下面不会讲解虚函数是怎么回事,如果对虚函数还不清楚的同学请先好好学习一下虚函数和继承重载的概念。

都知道c++里面类的成员函数如果不用virtual关键字,那么子类继承后,如果用基类指针指向子类对象,调用函数时其实还是调用的基类的,除非你把成员函数设为虚函数。用过java的同学肯定会觉得很坑,这尼玛不是多次一举吗?java的类成员方法默认都是虚函数。都知道多态的基石就是用基类指针在运行时指向子类的对象来实现多态,这样你就可以用统一的基类接口来定义一致的行为调用,然后在具体实现时只要换上自己的类就能在运行时完成各种不同的功能了。TT里面的imconn和其他loginConn, msgConn,proxyConn其实就是这样的关系。那么C++为什么不默认把类的成员函数都弄成虚函数呢?

c++的设计者真的会这么傻吗?让我们来看一段代码


// Example program
#include <iostream>
#include <string>
using namespace std;

class A1
{
private:
    int _a;
    virtual void _Add() {  //_Add是私有函数,用来完成一些内部的操作
        _a += 100;
    }
public:
    A1(int a) : _a(a) {}
    void out() {
        _Add();
        cout << _a << endl;
    }
};

class A2 : public A1
{
private:
    int _a;
    void _Add()  {  //这里假设A2类的开发者并不知道A1里也有个_Add,意外重名
        _a += 1000;
    }
public:
    A2(int a) : A1(a), _a(a+1) {}
    void out() {
        _Add();
        A1::out();
        cout << _a << endl;
    }
};

int main()
{
    A2 a(5);
    a.out();
}

先别编译运行,想一想这个程序的输出应该是什么? 105 1006?

好吧来运行一下,结果是

5 2006

是不是很差异?

基类的out明明不是先加了100再cout的吗?

想一想2006哪儿来的?难道是基类的_Add实际运行时执行的是子类的_Add?

没错,就是这样的。在子类里调用A1::out,A1::out里会调用_Add,而这个_Add由子类的虚函数表(VFT)指向了子类的_Add函数。

好了,问题来了,子类的实现者其实并不知道基类里面有个_Add,只是这个函数名字太通俗,一不小心重名了,然后意外的改写了基类out的运行。即使基类这个函数定义为私有的也没办法。这就破坏了面向对象的一个重要原则——封装性。 子类在继承基类的过程中破坏了基类的函数行为。

那么怎么解决这个问题?把基类的_Add前面的virtual去掉,好了,一切如你想要的那样运转,输出105 1006

现在我们可以说,c++为什么要区分virtual和非virtual成员函数了。也终于可以清楚的知道什么时候改在成员函数前加virtual,什么时候不需要。

总结一下,当你的基类里需要写一些私有的,只在基类内部运行的函数时,千万别给它加上virtual,否则,如果你的继承者运气不太好,意外写了个同名的函数,那么就等着出一些莫名其妙的查死他的问题吧。甚至有时候这种问题根本就没法查,因为他有可能根本就没有你的基类的源代码。

当然,如果你想坑那货,比如你因为对当前的公司和同事深恶痛绝,正准备离职,又想不到有什么方法能整死他们,并且当他们最终发现时又不能将责任怪罪于你,那就在基类里定义这种名字很容易冲突的内部运行函数,并且给它加上virtual,然后当你同事在你离职后苦逼的最终把原因找到正想找你算账时,你可以回他一句,谁让你人品不好,定义一个跟我一样的函数名呢?

别说是我这里教你的。。。




© 著作权归作者所有

共有 人打赏支持
笨笨_蛋蛋
粉丝 34
博文 6
码字总数 11619
作品 0
南京
私信 提问
C++对象内存模型---数据封装

“C++是C的超集”,这句话里包含了两个问题: 1. C++兼容C; 2. C++在C之上进行了扩展; 那C++在C之上进行了哪些扩展呢?众所周知,C++内置了众多编程机制、支持众多编程风格:面向过程、基于...

rexlv
2013/05/05
0
0
C++ 虚函数机制分析

原文:C++ 虚函数机制分析 作者:Breaker C++ 中的虚函数调用机制通常是靠虚函数表 (vtbl) 和虚表指针 (vptr) 实现的,调用行为称为 晚绑定 (later binding)、动态绑定 (dynamic binding) 或...

晨曦之光
2012/05/23
476
0
深入浅出MFC学习笔记:(第一章:win32基本概念,第二章:C++的重要性质)

深入浅出MFC阅读笔记: 写在开始的话: 刚结束《C++primer》的第二次阅读,决定趁热打铁,学习《深入浅出MFC》。当然,学习框架不是目的,而是通过学习MFC底层框架实现原理,对C++面向对象思...

长平狐
2012/10/08
151
0
C++灵魂所在之---多态的前世与今生

开头先送大家一句话吧: 众所周知,在20世纪80年代早期,C++在贝尔实验室诞生了,这是一门面向对象的语言,但它又不是全新的面向对象的语言,它是在传统的语言(C语言)进行面向对象扩展而来...

loving_forever_
2016/06/13
0
0
C++十种方法"Hello World"

初学编程,无论是VB,C/C++,Java,C#大多都是从Hellow World这个程序开 始的,也是最常见的入门方法。C/C++本身有很多特性和用发,这里就用十种方法 实现Hellow World这个程序. 1. 最经典的...

程序鸡
2012/12/07
0
3

没有更多内容

加载失败,请刷新页面

加载更多

Linux iptables之mangle表使用案例

mangle表的用途 mangle表的主要功能是根据规则修改数据包的一些标志位,以便其他规则或程序可以利用这种标志对数据包进行过滤或策略路由。 mangel表使用示例 示例1-策略路由1 内网的客户机通...

月下狼
今天
3
0
OSChina 周日乱弹 —— 兼职我想去学学布偶戏

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @clouddyy : 《火炎 - 女王蜂》 《火炎 - 女王蜂》 手机党少年们想听歌,请使劲儿戳(这里) @小鱼丁 :还在睡觉突然接到一个小哥哥电话“x...

小小编辑
今天
105
7
租房软件隐私保护如同虚设

近日,苏州市民赵先生向江苏新闻广播新闻热线025-84658888反映,他在“安居客”手机应用软件上浏览二手房信息,并且使用该软件自动生成的虚拟号码向当地一家中介公司进行咨询。可电话刚挂不久...

linux-tao
今天
4
0
分布式项目(五)iot-pgsql

书接上回,在Mapping server中,我们已经把数据都整理好了,现在利用postgresql存储历史数据。 iot-pgsql 构建iot-pgsql模块,这里我们写数据库为了性能考虑不在使用mybatis,换成spring jd...

lelinked
今天
6
0
一文分析java基础面试题中易出错考点

前言 这篇文章主要针对的是笔试题中出现的通过查看代码执行结果选择正确答案题材。 正式进入题目内容: 1、(单选题)下面代码的输出结果是什么? public class Base { private Strin...

一看就喷亏的小猿
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部