??xml version="1.0" encoding="utf-8" standalone="yes"?>
如:const int a;和int const bQ中Q虽然两U具有相同的意义Q但是最好利用后面的那种情况Q后面的更加可读Q因为:int const我们可以很清楚地看到const是修饰int,而前面的那种Ҏ中,我们׃那么L知道到底const 的具体涵义。由后面的那U方法我们可以知道const指一个恒定的整Ş。Int *const bQ指的是一个恒定的指针bQ这个指针指向一个整型,所以这个指针的内容可以改变Q但是它的指针g是b的g能变Q相应的int const *bQ指的是一个指向恒定整型内容的指针bQ也是说这个b的内容可以变Q但是开始的b所指向的地址中的内容不能在程序当中通过b来改变?br />
volatile对上面的原则也适用?br />使用cdT做ؓcd变量已经作ؓ了模板参数的惯例Q用来表C函数或者类所接受的类型参量可以用所有的cd?br />
在C++的模板的cd参量前,最好尽量用typename来代替class?br />在编译时期,模板被编译两ơ;
实例化之前:查模板代码本w,查看语法是否正确Q?br />在实例化期间Q检查模板代码,查看是否所有的调用都有效?br />
当用函数模板,q且引发模板实例化的时候,~译器需要查看模板定义?br />在函数实参的cd的推gQ如果类型出C匚w现象则会出现~译错误。如果要解决q编译错误则有以下几U方法解冻I
对传入的实参q行cd转换成匹配类型后传入?br />昄指定模板函数的全特化Q不能ؓ偏特化,因ؓ函数不支持偏特化Q类型?br />例子如下所C:
在模板函数内部不能指定默认的模板参数?br />函数不能采用偏特化的Ҏ来实现类型的递归Q但是它可以利用函数重蝲的方法来实现cd的{换?br />
相对?而言c能使用偏特化的Ҏ来实现类型的递归Qƈ且它的仿函数也可以用重载operator()来实现函数重载方法。但是它的一个问题就是在调用仿函数的时候一定要加上它的实例化参数类型,以及调用它的构造函数?br />
函数调用的时候可以采用由参数的类型来反推函数的模板参敎ͼq是仿函数所不能的。所以我们在~程的过E当中一定要注意q些不同技术之间的优点和不I看看哪些更适合我们?br />
在调用非标准函数的时候最好要与调用标准函数区分开来,q样不致于用程序生歧义的错误。做法是Q在变量或者函数的前面加上全局标识W?:?/font>
联系?赵小? 联系电话:
白天: 025-83909202(灵?
晚上: 025-83408282
Email:xiaomeng_zhao#yahoo.com.cn
(注意上面Email的将#换成@?)
转让如下(南京以外的城市可能要邮购?:
最新消?- 新增3本超值特价书
--------------------------
1. 人邮出版?Visual C++ |络开发技?
2. 清华出版?Windows 2000/XP中文版注册表使用开发与案例"
3. 中国水利水电出版C"ATM|络技?
--------------------------
英文书名: Sams Teach Yourself C# in 21 Days
中文书名: 21天学通C#
作? Bradley L. Jones
授权出版C? Sams
被授权出版社: 人民邮电出版C?br />成色: 全新
原h: 60?br />折扣? 同ؓM? 您看打几折合适吧
Ch: 您看着l吧:)
英文书名: Programming Microsoft Windows with Microsoft Visual Basic .Net
中文书名: Microsoft WindowsE序设计 - Visual Basic .NET语言描述
作? Charles Petzold (?章立?译)
授权出版C? Microsoft
被授权出版社: 华中U技大学出版C?br />成色: 全新
原h: 118?br />折扣? 同ؓM? 您看打几折合适吧
Ch: 您看着l吧:)
书名: _NVisual Basic .NET中文?br />作? 刘炳文教?谭浩强工作室)
出版C? 机械工业出版C?br />成色: 全新
原h: 66?br />折扣? 同ؓM? 您看打几折合适吧
Ch: 您看着l吧:)
书名: PowerBuilder应用与开?br />出版C? 机械工业出版C?br />成色: 全新
原h: 49?br />折扣? 同ؓM? 您看打几折合适吧
Ch: 您看着l吧:)
书名: 中文Visual Basic高~程
出版C? 清华大学出版C?br />成色: 几乎全新
原h: 29.8
折扣? 同ؓM? 您看打几折合适吧
Ch: 您看着l吧:)
书名: Visual Basic实用技术指?br />出版C? 人民邮电出版C?br />成色: 9成新
原h: 38?br />折扣? 同ؓM? 您看打几折合适吧
Ch: 您看着l吧:)
极品匿名ftp
ftp://219.157.126.10
pFTP(太原理工大学l极|域FTP)
ftp://finalmov:finalmov@202.207.240.119
荥经电信(l合,速度?
极品匿名ftp(电媄׃c?内容丰富)
l合FTP
ftp://www.mx3x.com
:< wind_code_1 >
高速ftp,不要密码,东西暴多!!!
ftp://221.224.20.206
武汉安全|ftp
ftp://315safe:315safe@ftp.315safe.com
KingSExftp 带宽Q网?00m
ftp://bbs.winzheng.com:loveyou@218.56.97.189
pq箋剧音乐FTPQ?
QUOTE:
ftp://221.224.20.206
好的音乐FTP:
ftp://219.153.2.114/
非匿名 ?石狮p的时帐P
ftp://石狮p免费下蝲:bbs.yssn.com石狮p@ftp1.ssdvd.com:21
蓝魂dFTP:
ftp.bluesma.com:3131
帐号Q蓝主力服务器
密码Q蓝论坛精彩无?/font>
http://www.bluesma.com[/url
]
匿名电媄ftp
202.102.234.88
兰荫补档Z??
ftp://ly_budang:reqisthebest123@ftp1.lanyin.net/
天香APE音乐
ftp://rt_admin:3166admin@hclhcl7.vicp.net:3000
烟雨江南pFTP
ftp://烟雨江南:bbs.tc.cn@ftp.wolf2000.com/
ftp站点QdvdQ?
ftp://dvd:dvd@210.34.14.166:2121
ftp://ydy.com.high:qSDMPJASo7XTBz@ftp.tzssyxx.com/
ftp://shiyushen.vicp.net
用户名:down
密码Qfox**fox**down
学校教育的FTP:
QUOTE:
匿名ftp(化学数学软g{?
ftp://210.34.15.126/pub/
香港中文大学Q用Flashfxp的请“用被动模式”前面的勑֏消就可以d了。)
ftp://ftp.cs.cuhk.edu.hk
ftp://fszc1@57333.com
ftp://fpdayl1@57333.com
交大FTP
ftp://cate:73610@202.117.28.20/
三明高等专科学校
ftp://smu:smc@218.5.241.10/
湖北国土资源职业学院下蝲
江大学q程教学ftp
ftp://teach.zj.edu.cn
账号Q?ftpuser
密码Q?xt38kma7
江q播电视大学q阳分校FTP
q个论坛实在是好呀Q特把自己收藏的教育|内的ftp拿出来给大家分nQ?
1.
ftp://scau:scau@ftp1.scau.edu.cn:8021
;游戏QY?Q“用被动模式”!Q?
2. ftp://scau:scau@ftp2.scau.edu.cn:8021 ;电视?
3. ftp://scau:scau@ftp3.scau.edu.cn:8021 ;电媄
一个学习的ftpQ目录ؓQsf图书、Software、studyQ单U程Q速度一般?
ftp://sf:xjtumba@swds.3322.org
一个高校的ftpQ资料下载帐?Q用“被动模式”?
ftp://61.153.29.19
同济大学的电子书库FTP
ftp://202.120.165.151
成都理工大学
ftp://ftp.cdut.edu.cn
教授论坛FTP
一高中的FTP里面有好多东东,速度200k
ftp://teacher:lg2005@ftp.lxyg.net
香港中文大学Ftp
ftp://ftp.cuhk.hk
FTPQ(学习Q游戏,电媄Q?
ftp://211.86.94.57
ftp://211.86.49.66
ftp://edutc1.hyit.edu.cn
q?个指向同一目录?
ftp://210.27.234.1
ftp://202.117.47.49
q两也个指向同一目录
甘肃电大FTP
ftp://61.178.59.209/
内容Q常用工兯YӞ电大数字图书馆,直播译֠资料Q课件和四六U资料等
可以多线E下载,速度极快?
软g工具和教E类:
QUOTE:
多软g下蝲一应俱?
ftp://ftp.hbgt.com.cn
一个Y件FTP
ftp://61.132.90.12/
华军专用FTP
地址:
ftp://soft.pcsoft.com.cn
用户?soft
密码:softsoft
很多软g,包括很多|管需要的软g(由TuTu2008提供)
ftp://61.152.101.129
用户?6288
密码?16288
有效FTP包括LINUX和破?
ftp://fikusabe:928604@668y.com
发个多软g的FTP,不用用户名的,不信的看?看了?q下!
ftp://221.224.20.206
龙联论坛
ftp://down:bbs.51vip.net@downbbs.51vip.cn:29
软g和ASP源码FTP 刚发现的
ftp://downaspskynet:downaspskynet @< wind_code_2 >
播放软g、插件、工具下载(非视频下载)
ftp://hdtvsoft:hdtvsoft@ftp.hd-tv.cn
学习教材Q?Qlsq12345Q用迅雷依然能下。)
http://bbs.mumayi.net/viewthread ... &extra=page%3D1
魔术视频表演、教E?
电子图书书库 FTP下蝲3.12GQ连接慢Q?
杂志FTP(电子?
ftp://tlfadv:deiejhjhhjt@tlfs90.3322.org:9988
深圳信息|FTP
ftp://cd:media@media.szwebs.net
驱动之家׃nFTP
ftp://driversd:BBSDrivers1724@218.28.45.166/
常州市科技信息中心FTP
--------------------------------
大量_ֽFTP站点:
GNUCHINAFTP服务?/font>
ftp.gnuchina.org
提供各种Linux*作系l镜像和应用软g供下载,不限制h敎ͼ但限制线E?
谷动力FTP服务?/font>
ftp.esoftware.com.cn/
由ENET丑֊的FTP站点Q提供各U共享Y件、教E、代码下载,允许匿名讉K和多U程下蝲?
中国下蝲FTP服务??/font>
ftp.download.com.cn/
提供各种׃n软g、免费Y件供下蝲Q允许匿名访问,人数限制2650?
中经|自pY件FTP服务器freesoft.cei.gov.cn/
׃国经信息网丑֊Q除提供了大量的Linux软g外,q设有众多的FTP站点镜像目录Q允许匿
名访问?
中国下蝲FTP服务?号ftp1.download.com.cn/
提供各种׃n软g、免费Y件供下蝲Q允许匿名访问,人数限制1650Q同时提供http|页?
航?
黄金眼FTP服务?02.205.10.22/
提供大量的游戏供下蝲Q分为策略、动作、格斗、即时、角艌Ӏ冒险、模拟、体育等目录Q允?
匿名讉K?
EastDoor亦多下蝲中心FTP服务?02.113.29.120/
提供Linux、Windows、Solaris、ISO文g{Y件和电媄供下载,允许匿名讉KQ每个IP最大线E?
?Qh数限?00?
TurboLinuxFTP服务?/font>
ftp.turbolinux.com.cn/
提供TurboLinux*作系l和各种Linux应用软g供下载,允许匿名讉K?
蓝皮鼠时FTP服务器cn-ftp.dhs.org/
提供各种软g、资料文档下载,允许匿名讉KQ单个IPU程最高ؓ1?
深圳热线FTP服务?/font>
ftp.szonline.net/
提供各种软g、游戏、编E资料下载,允许匿名讉KQh数限?0?
中国l济信息|FTP服务?/font>
ftp.cei.gov.cn/
提供l济cY件和研究资料下蝲Q允许匿名访问?
中国工程技术FTP服务?/font>
ftp.cetin.net.cn/
提供各种应用软g、编EY件供下蝲Q允许匿名访问?
166.111.174.33(电媄QY?
166.111.184.48(电媄Q游戏,日剧)
166.111.215.143(东西很少Q但是有机器?-49集下?
166.111.215.175(东西不多Q但是有不少大Y?
166.111.106.144(东西很杂Q连上后qPUB目录Q然后看内容提要?
166.111.107.91(音乐QY?
166.111.136.248(好象都是一?D的东?
166.111.141.38(软g)
166.111.142.6(不少大的软gQ音?CD是放Y件的目录)
166.111.147.51(音乐Q特别是国外音乐很多Q可惜限了?00K)
166.111.136.2(不少大Y件和ISO的东?
166.111.14.199(软gQ电影,MP3Q还有一些BOOK)
166.111.25.5(软gQ电影,q箋?走遍国{…?
166.111.2.114(软g)
166.111.26.145(正在学习英文的朋友可以来q里,不少书喔)
166.111.33.64(东西很少QYӞ电媄)
166.111.36.151(不少好的软g?.)
166.111.37.6(软g)
166.111.37.82(软g)
166.111.41.149(音乐)
166.111.41.172(音乐和一些电q片D?
166.111.49.106(书刊)
166.111.60.198(软g)
166.111.53.55(书刊Q教E?
166.111.53.84(很多ISO的好东东)
166.111.55.29(软g)
166.111.61.200(电媄)
166.111.61.232(军事)
166.111.62.19(不少ISO的大东东Q其中包括有MSDN)
166.111.62.245(音乐)
166.111.70.51(软g)
q年来,利用Internetq行|际间通讯,在WWW?览、FTP、Gopherq些常规服务Q以及在|络电话、多媒体会议{这些对实时性要求严?的应用中成ؓ研究的热点,而且已经是必需的了。Windows环境下进行通讯E序设计的最基本Ҏ是应用Windows Sockets实现q程间的通讯Qؓ此微软提供了大量ZWindows Sockets的通讯APIQ如WinSockAPI、WinInetAPI和ISAPIQƈ一直致力于开发更快?更容易的通讯APIQ将其和MFC集成在一起以佉K讯~程来容易。本实例重点介绍使用MFC的CSocketcȝ写网l通讯E序的方法,q过使用CSocketcdC|络聊天E序。程序编译运行后的界面效果如图一所C: ![]()
一、实现方?/font> 微Y的MFC把复杂的WinSock API函数装到类里,q得编写网l应用程序更Ҏ。CAsyncSocketc逐个装了WinSock APIQؓ高|络E序?提供了更加有力而灵zȝҎ。这个类ZE序员了解网l通讯的假设,目的是ؓ了在MFC中用WinSockQ程序员有责d理诸如阻塞、字节顺序和在Unicode与MBCS 间{换字W的d。ؓ了给E序员提供更方便的接口以自动处理q些dQMFCl出 了CSocketc,q个cL由CAsyncSocketcȝ承下来的Q它提供了比CAsyncSocket更高层的WinSock API接口。CsocketcdCsocketFilecd以与CarchivecM起合作来理发送和接收的数据,qɽ理数据收发更加便利。CSocket对象提供d模式Q这对于Carchive的同步操作是臛_重要的。阻塞函敎ͼ如Receive()、Send()、ReceiveFrom()、SendTo() 和Accept()Q直到操作完成后才返回控制权Q因此如果需要低层控制和高效率,׃用CasyncSockc;如果需要方便,则可使用CsocketcR? 一些网l应用程?如网l电话、多媒体会议工具)对实时性要求非常强Q要求能够直接应用WinSock发送和接收数据。ؓ了充分利用MFC 的优势,首选方案应当是MFC中的CAsyncSocketcLCSocketc,q两个类完全装了WinSock APIQƈ提供更多的便利。本实例介绍应用q两个类的编E模型,q引出相关的成员函数与一些概늚解释? CSocketcL由CAsyncSocketl承而来的,事实上,在MFC中CAsyncSocket 逐个装了WinSock APIQ每个CAsyncSocket对象代表一个Windows Socket对象Q用CAsyncSocket c要求程序员对网l编E较为熟悉。相比v来,CSocketcLCAsyncSocket的派生类Q?l承了它装的WinSock API?/font> 一个CSocket对象代表了一个比CAsyncSocket对象更高层次的Windows Socket的抽象,CSocketcMCSocketFilecdCArchivecM起工作来发送和接收数据Q因此用它更加Ҏ使用。CSocket对象提供d模式Q因为阻塞功 能对于CArchive的同步操作是臛_重要的。在q里有必要对d的概念作一解释Q?一个socket可以处于"d模式"?非阻塞模?Q当一个套接字处于d模式Q即同步操作Q时Q它的阻塞函数直到操作完成才会返回控制权Q之所以称为阻塞是因ؓ此套接字的阻塞函数在完成操作q回之前什么也不能做。如果一个socket处于非阻塞模式(卛_步操作)Q则会被调用函数立即q回。在CAsyncSocketcM可以用GetLastError 成员函数查询最后的错误Q如果错误是WSAEWOULDBLOCK则说明有dQ而CSocketl不会返回WSAEWOULDBLOCKQ因为它自己理d。微软徏议尽量用非d模式Q通过|络事g的发生而通知应用E序q行相应的处理。但在CSocketcMQؓ了利用CArchive 处理通讯中的许多问题和简化编E,它的一些成员函数Lhd性质的,q是因ؓCArchivec需要同步的操作?/font> 在Win32环境下,如果要用具有阻塞性质的套接字Q应该放在独立的工作U程中处理,利用多线E的Ҏ佉K塞不至于q扰其他U程Q也不会把CPU旉费在阻塞上。多U程的方法既可以使程序员享受CSocket?来的化编E的便利Q也不会影响用户界面对用L反应? CAsyncSocketcȝE模?/font> 在一个MFC应用E序中,要想L处理多个|?l协议,而又不牺牲灵zL时Q可以考虑使用CAsyncSocketc,它的效率比CSocket c要高。CAsyncSocketc针对字节流型套接字的编E模型简q如下: 1、构造一个CAsyncSocket对象Qƈ用这?对象的Create成员函数产生一个Socket句柄。可以按如下两种Ҏ构造:
或在指定端口号生一个数据报套接?/font>
W一U方法在栈上产生一个CAsyncSocket对象Q?而第二种Ҏ在堆上生CAsyncSocket对象Q第一U方法中CreateQ)成员函数用缺省参C生一个字节流套接字,W二U方法中用CreateQ)成员函数在指定的端口产生一个数字报套接字。CreateQ)函数的原型ؓQ?/font>
该函数的参数有: 1Q端口,UINTcd。注意:如果是服务方Q则?用一个众所周知的端口供服务方连接;如果是客hQ典型做法是接受默认参数Q 套接字可以自主选择一个可用端口; 2Qsocket cdQ可以是SOCK-STREAMQ默认|字节)或SOCK-DGRAMQ数据报Q; 3Qsocket的地址Q例? ftp.gliet.edu.cn "?202.193.64.33"? 2、如是客hE序Q用CAsyncSocket∷ConnectQ)成员函数q接到服务方Q如是服务方E序Q用CAsyncSocket∷ListenQ)成员函数开?监听Q一旦收到连接请求,则调用CAsyncSocket∷AcceptQ)成员函数开始接收。注意:CAsyncSocket ∷AcceptQ)成员函数要用一个新的ƈ且是I的CAsyncSocket对象作ؓ它的参数Q这里所??I的"指的是这个新对象q没有调用CreateQ)成员函数? 3、调用其他的CAsyncSocketcȝReceiveQ)、ReceiveFromQ)、SendQ)和SendToQ){成员函数进行数据通信? 4、通讯l束后,销毁CAsyncSocket对象。如果是在栈上生的CAsyncSocket对象Q则对象出定义的范围时自动被析构;如果是在堆上产生Q也是用了newq个操作W,则必M用delete操作W销毁CAsyncSocket 对象? CSocketcȝE模? 使用CSocket对象涉及CArchive和CSocketFile cd象。以下介l的针对字节型套接字的操作步骤中,只有W?步对于客h和服务方操作是不同的Q其他步骤都相同? 1、构造一个CSocket对象? 2、用这个对象的CreateQ)成员函数产生一个socket对象。在客户方程序中Q除非需要数据报套接字,CreateQ)函数一般情况下应该使用默认参数。而对于服务方E序Q必d调用Create时指定一个端口。需要注意的是,Carchivecd象不能与数据报(UDPQ套接字一起工作,因此对于数据报套接字QCAsyncSocket和CSocket 的用方法是一L? 3、如果是客户方套接字Q则调用CAsyncSocket ∷ConnectQ)函数与服务方套接字连接;如果是服务方套接字,则调用CAsyncSocket∷ListenQ)开始监听来自客h的连接请求,收到q接h后,调用CAsyncSocket∷AcceptQ)函数接受hQ徏立连接。请注意AcceptQ)成员函数需要一个新的ƈ且ؓI的CSocket对象作ؓ它的参数Q解释同上? 4、生一个CSocketFile对象Qƈ把它与CSocket 对象兌h?/font> 5、ؓ接收和发送数据各产生一个CArchive 对象Q把它们与CSocketFile对象兌h。切记CArchive是不能和数据报套接字一起工作的? 6、用CArchive对象的ReadQ)、WriteQ){函数在客户与服务方传送数据?/font> 7、通讯完毕后,销毁CArchive、CSocketFile和CSocket对象? 二、编E步?/font> 1?启动Visual C++6.0Q生成一个基于对话框架的应用E序Q将该程序命名ؓ"Test"Q?/font> 2?按照图一所C的效果图设|对话框的界面; 3?使用Class Wizard为应用程序的按钮d鼠标单击消息响应函数Q?/font> 4?使用Class Wizard在应用程序中定义新类CNewSocketQ其基类选择为CSocketQ?/font> 5?d代码Q编译运行程序?/font>
三、程序代?br />
四、小l?/font> 本实例介l了CAsyncSocket、CSocketc,q过使用CSocketcdC|络聊天E序。读者朋友还可以通过MFC CArchive 对象q行信息的接发操作,使得|络传输如同使用MFC的文档连载协?Serialization protocol)Q简h用?/font> |
Consider the following Point3d class declaration. It declares a virtual function, a static data member, and three coordinate values:
class Point3d {
public:
virtual ~Point3d(); //虚表指针的位|?非头卛_"
// ...
protected:
static Point3d origin;
float x, y, z;
};
What does it mean, then, to take the address of one of the coordinate members? For example, what value should the following yield?
&3d_point::z;
It is going to yield the z-coordinate's offset within the class object. Minimally, this has to be the size of the x and y members, since the language requires the members within an access level be set down in the order of declaration.
3.6 Pointer to Data Members
Pointers to data members are a somewhat arcane but useful feature of the language, particularly if you need to probe at the underlying member layout of a class. One example of such a probing might be to determine if the vptr is placed at the beginning or end of the class. A second use, presented in Section 3.2, might be to determine the ordering of access sections within the class. As I said, it's an arcane, although potentially useful, language feature.
Consider the following Point3d class declaration. It declares a virtual function, a static data member, and three coordinate values:
class Point3d {
public:
virtual ~Point3d();
// ...
protected:
static Point3d origin;
float x, y, z;
};
The member layout for each Point3d class object contains the three coordinate values in the order x, y, z and a vptr. (Recall that origin, the static data member, is hoisted outside the individual class object.) The only implementation aspect of the layout is the placement of the vptr. The Standard permits the vptr to be placed anywhere within the object: at the beginning, at the end, or in between either of the three members. In practice, all implementations place it either at the beginning or at the end.
What does it mean, then, to take the address of one of the coordinate members? For example, what value should the following yield?
&3d_point::z;
It is going to yield the z-coordinate's offset within the class object. Minimally, this has to be the size of the x and y members, since the language requires the members within an access level be set down in the order of declaration.
At the compiler's discretion, however, the vptr may be placed either before, in-between, or after the coordinate members. Again, in practice, the vptr is either placed at the beginning or at the end of the class object. On a 32-bit machine, floats are 4 bytes each, so we would expect the value to be either 8 bytes without an intervening vptr or 12 bytes with it. (The vptr, and pointers in general, use 4 bytes on a 32-bit architecture.)
That expectation, however, is off by one—a somewhat traditional error for both C and C++ programmers.
The physical offset of the three coordinate members within the class layout are, respectively, either 0, 4, and 8 if the vptr is placed at the end or 4, 8, and 12 if the vptr is placed at the start of the class. The value returned from taking the member's address, however, is always bumped up by 1. Thus the actual values are 1, 5, and 9, and so on. Do you see why Bjarne decided to do that?
The problem is distinguishing between a pointer to no data member and a pointer to the first data member. Consider for example:
float Point3d::*p1 = 0;
float Point3d::*p2 = &Point3d::x;
// oops: how to distinguish?
if ( p1 == p2 ) {
cout << " p1 & p2 contain the same value ?";
cout << " they must address the same member!" << endl;
}
To distinguish between p1 and p2, each actual member offset value is bumped up by 1. Hence, both the compiler (and the user) must remember to subtract 1 before actually using the value to address a member.
Given what we now know about pointers to data members, we find that explaining the difference between
&Point3d::z;
and
&origin.z
is straightforward. Whereas taking the address of a nonstatic data member yields its offset within the class, taking the address of a data member bound to an actual class object yields the member's actual address in memory. The result of
&origin.z
adds the offset of z (minus 1) to the beginning address of origin. origin是个实例化的cPoint3d的静态数据成? The value returned is of type
float*
not
float Point3d::*
because it refers to an specific single instance(静态成员属于类而非cȝ各具体实例对?, much the same as taking the address of a static data member.
Under multiple inheritance, the combination of a second (or subsequent) base class pointer to a member bound to a derived class object is complicated by the offset that needs to be added. For example, if we have
struct Base1 { int val1; };
struct Base2 { int val2; };
struct Derived : Base1, Base2 { ... };
void func1( int d::*dmp, d *pd )
{
// expects a derived pointer to member
// what if we pass it a base pointer?
pd->*dmp;
}
void func2( d *pd )
{
// assigns bmp 1
int b2::*bmp = &b2::val2;
// oops: bmp == 1,
// but in Derived, val2 == 5
func1( bmp, pd )
}
bmp must be adjusted by the size of the intervening Base1 class when passed as the first argument to func1(). Otherwise, the invocation of
pd->*dmp;
within func1() will access Base1::val1, not Base2::val2 as the programmer intended. The specific solution in this case is
// internal transformation by compiler
func1( bmp + sizeof( Base1 ), pd );
In general, however, we cannot guarantee that bmp is not 0 and so must guard against it:
// internal transformation
// guarding against bmp == 0
func1( bmp ? bmp + sizeof( Base1 ) : 0, pd );
? Efficiency of Pointers to Members:
The following sequence of tests attempts to gain some measure of the overhead associated with using pointers to members under the various class representations of the 3D point. In the first two cases, there is no inheritance. The first case takes the address of a bound member:
float *ax = &pA.x;
for the three coordinate members of points pA and pB. The assignment, addition, and subtraction look as follows:
*bx = *ax - *bz;
*by = *ay + *bx;
*bz = *az + *by;
The second case takes the address of a pointer to data member:
float pt3d::*ax = &pt3d::x;
for the three coordinate members. The assignment, addition, and subtraction use the pointer to data member syntax, binding the values to the objects pA and pB:
pB.*bx = pA.*ax - pB.*bz;
pB.*by = pA.*ay + pB.*bx;
pB.*bz = pA.*az + pB.*by;
Recall that the direct data member exercise of this function, executed in Section 3.5, ran with an average user time of 0.80 with optimization turned on and 1.42 with optimization turned off for both compilers. The results of running these two tests, coupled with the results of the direct data access, are shown in Table 3.3:
Table 3.3. Nonstatic Data Member Access
Optimized Non-optimized
Direct Access 0.80 1.42
Pointer to
Bound Member 0.80 3.04
Pointer to
Data Member
CC 0.80 5.34
NCC 4.04 5.34
The non-optimized results conform to expectations. That is, the addition of one indirection per member access through the bound pointer more than doubles the execution time. The pointer-to-member access again nearly doubles the execution time. The binding of the pointer to data member to the class object requires the addition of the offset minus 1 to the address of the object. More important, of course, the optimizer is able to bring the performance of all three access strategies into conformance, except the anomalous behavior of the NCC optimizer. (It is interesting to note here that the appalling performance of the NCC executable under optimization reflects a poor optimization of the generated assembly code and not an attribute of the source-level C++ code. An examination of the generated non-optimized assembly for both CC and NCC showed the two outputs to be identical.)
The next set of tests looks at the impact of inheritance on the performance of pointers to data members. In the first case, the independent Point class is redesigned into a three-level single inheritance hierarchy with one coordinate value as a member of each class:
class Point { ... }; // float x;
class Point2d : public Point { ... }; // float y;
class Point3d : public Point2d { ... }; // float z;
The next representation retains the three-level single inheritance hierarchy but introduces one level of virtual inheritance: the Point2d class is virtually derived from Point. As a result, each access of Point::x is now accessing a virtual base class data member. Then, more out of curiosity than need, the final representation added a second level of virtual inheritance, that of Point3d being virtually derived from Point2d. Table 3.4 shows the results. (Note: The poor performance of the NCC optimizer was consistent across the tests, so I've left it off the listing.)
Table 3.4. Pointer to Data Member Access
Optimized % Non-optimized
No Inheritance 0.80 5.34
SI (3 levels) 0.80 5.34
VI (1 level) 1.60 5.44
VI (2 level) 2.14 5.51
SI: Single Inheritance VI: Virtual Inheritance
Because inherited data members are stored directly within the class object, the introduction of inheritance does not affect the performance of the code at all. The major impact of introducing virtual inheritance is to impede the effectiveness of the optimizer. Why? In these two implementations, each level of virtual inheritance introduces an additional level of indirection. Under both implementations, each access of Point::x, such as
pB.*bx
is translated into
&pB->__vbcPoint + ( bx - 1 )
rather than the more direct
&pB + ( bx - 1 )
The additional indirection reduced the ability of the optimizer to move all the processing into registers.
如果取这个类的大,可以看到l果?而不??br />下面声明q个cȝ一个实例,q取得其VTABLE中第一个元素的|
注意取值的q一行运用了复杂的强制类型{换。我把它拆开解释一下。首先是取得对象pC的前四个字节的内容,只要把pC转换成int*然后直接取值就行了Q?br /> *(int*)pC
下一步是把取得的q个值当作是一个指针,也就是再q行一ơ强制类型{换:
(int*)(*(int*)pC)
最后取q个指针所指内存的内容Q也是VTABLE中第一个函数的地址了:
*(int*)(*(int*)pC)
取得q个地址以后Q下面就用汇~代码来调用q个地址所指的函数Q?br />
注意调用cȝ非静态成员函数时需要先把对应实例的地址攑ֈECX寄存器中Q也是q_所说的“隐藏参数”了Q然后ؓ函数Print压两个参数进栈,Ҏq行的结果可以明昄出来调用cL员函数时也是从右向左压栈的,最后用call语句调用函数。不隑֏现调用类成员函数在参C数确定时也是p调用者负责弹栈,看来cL员函C是可以声明ؓ参数个数可变的函C?br />
最后,执行E序Q得到结果:
i=1 a=2 b=3
An obvious observation is that without the optimizer turned on, it is extremely difficult to guess at the performance characteristics of a program, since the code is potentially hostage to the "quirk(s) of code generation…unique to a particular compiler." Before one begins source level "optimizations" to speed up a program, one should always do actual performance measurements rather than relying on speculation and common sense.不要惛_? 要试验之.
In the next sequence of tests, I introduced first a three-level single inheritance representation of the Point abstraction and then a virtual inheritance representation of the Point abstraction. I tested both direct and inline access (multiple inheritance did not fit naturally into the model, so I decided to forego it.) The general hierarchy is
class Point1d {...}; // maintains x
class Point2d : public Point1d {...}; // maintains y
class Point3d : public Point2d {...}; // maintains z
The one-level virtual inheritance derived Point2d virtually from Point1d. The two-level virtual inheritance additionally derived Point3d virtually from Point2d. Table 3.2 lists the results of running the tests for both compilers. (Again, I break out the times for the two compilers only when their performances differ significantly from each other's.)
Table 3.2. Data Access under Inheritance Models
Optimized Non-optimized
Single Inheritance
Direct Access 0.80 1.42
Inline Methods
CC 0.80 2.55
NCC 0.80 3.10
Virtual Inheritance ?1-Level
Direct Access 1.60 1.94
Inline Methods
CC 1.60 2.75
NCC 1.60 3.30
Virtual Inheritance ?2-Level
Direct Access
CC 2.25 2.74
NCC 3.04 3.68
Inline Methods
CC 2.25 3.22
NCC 2.50 3.81
Single inheritance should not affect the test performance, since the members are stored contiguously within the derived class object and their offsets are known at compile time. The results, as expected, were exactly the same as those of the independent abstract data type. (The same should be true under multiple inheritance, but I didn't confirm that.)
Again, it is worth noting that with the optimizer off, performance, which common sense says should be the same (direct member access versus inline access), is in practice slower in the case of inline functions. The lesson again is that the programmer concerned with efficiency must actually measure the performance of his or her program and not leave the measurement of the program to speculation and assumption. It is also worth noting that optimizers don't always work. I've more than once had compilations fail with an optimizer turned on that compiled fine "normally."别想当然, 实验? ~译时尽可能打开优化开?
The virtual inheritance performance is disappointing in that neither compiler recognized that the access of the inherited data member pt1d::_x is through a nonpolymorphic class object and that therefore indirect runtime access is unnecessary. Both compilers generate indirect access of pt1d::_x (and pt1d::y in the case of two levels of virtual inheritance), even though its location within the two Point3d objects is fixed at compile time. The indirection significantly inhibited the optimizer's ability to move all the operations within registers. The indirection did not affect the non-optimized executables significantly.
虚承导致性能大降, 即打开优化开关也没太大v?
class Concrete1 {
public:
// ...
protected:
int val;
char bit1;
};
class Concrete2 : public Concrete1 {
public:
// ...
protected:
char bit2;
};
class Concrete3 : public Concrete2 {
public:
// ...
protected:
char bit3;
};
From a design standpoint, this representation may make more sense. From an implementation standpoint, however, we may be distressed to find that a Concrete3 class object now has a size of 16 bytes—double its previous size.
What's going on? Recall that the issue is the integrity of the base class subobject within the derived class. Let's walk through the layout of the inheritance hierarchy to see what is going on.
The Concrete1 class contains the two members—val and bit1—that together take up 5 bytes. The size of a Concrete1 class object, however, is 8 bytes: the 5 bytes of actual size plus 3 bytes of padding to align the object on a machine word boundary. That's as true in C as it is in C++; generally, alignment constraints are determined by the underlying processor.
_心的程序员可要倒霉?
Nothing necessarily to complain about so far. It's the layout of the derived class that typically drives the unwary programmer into fits of either perplexity or angry indignation. Concrete2 adds a single nonstatic data member, bit2, of type char. Our unwary programmer expects it to be packed into the base Concrete1 representation, taking up one of the bytes otherwise wasted as alignment padding. This layout strategy makes the Concrete2 class object also of size 8 bytes, with 2 bytes of padding.
The layout of the Concrete2 class, however, instead preserves the 3 bytes of padding within the Concrete1 base class subobject. The bit2 member is set down after that, followed by an additional 3 bytes of padding. The size of a Concrete2 class object is 12 bytes, not 8, with 6 bytes wasted for padding. The same layout algorithm results in a Concrete3 class object's being 16 bytes, 9 of which are wasted on padding.
Why? Let's declare the following set of pointers:
Concrete2 *pc2;
Concrete1 *pc1_1, *pc2_2;
Both pc1_1 and pc2_2 can address objects of either three classes. The following assignment
*pc1_1 = *pc2_2;
should perform a default memberwise copy of the Concrete1 portion of the object addressed. If pc1_1 addresses a Concrete2 or Concrete3 object, that should not be of consequence to the assignment of its Concrete1 subobject.
However, if the language were to pack the derived class members Concrete2::bit2 or Concrete3::bit3 into the Concrete1 subobject, these language semantics could not be preserved. An assignment such as
pc1_1 = pc2;
// oops: derived class subobject is overridden
// its bit2 member now has an undefined value
*pc1_1 = *pc2_2;
would overwrite the values of the packed inherited members. It would be an enormous effort on the user's part to debug this, to say the least.
? Adding Polymorphism:
If we want to operate on a point independent of whether it is a Point2d or Point3d instance, we need to provide a virtual function interface within our hierarchy. Let's see how things change when we do that:
class Point2d {
public:
Point2d( float x = 0.0, float y = 0.0 )
: _x( x ), _y( y ) {};
// access functions for x & y same as above
// invariant across type: not made virtual
// add placeholders for z ?do nothing ...
virtual float z(){ return 0.0 };
virtual void z( float ) {}
// turn type explicit operations virtual
virtual void
operator+=( const Point2d& rhs ) {
_x += rhs.x(); _y += rhs.y(); }
// ... more members
protected:
float _x, _y;
};
It makes sense to introduce a virtual interface into our design only if we intend to manipulate two- and three-dimensional points polymorphically, that is, to write code such as
where p1 and p2 may be either two- or three-dimensional points. This is not something that any of our previous designs supported. This flexibility, of course, is at the heart of OO programming. Support for this flexibility, however, does introduce a number of space and access-time overheads for our Point2d class:
(1). Introduction of a virtual table associated with Point2d to hold the address of each virtual function it declares. The size of this table in general is the number of virtual functions declared plus an additional one or two slots to support runtime type identification.
(2). Introduction of the vptr within each class object. The vptr provides the runtime link for an object to efficiently find its associated virtual table.
(3). Augmentation of the constructor to initialize the object's vptr to the virtual table of the class. Depending on the aggressiveness of the compiler's optimization, this may mean resetting the vptr within the derived and each base class constructor. (This is discussed in more detail in Chapter 5.)
(4). Augmentation of the destructor to reset the vptr to the associated virtual table of the class. (It is likely to have been set to address the virtual table of the derived class within the destructor of the derived class. Remember, the order of destructor calls is in reverse: derived class and then base class.) An aggressive optimizing compiler can suppress a great many of these assignments.
Here is our new Point3d derivation:
class Point3d : public Point2d {
public:
Point3d( float x = 0.0, float y = 0.0, float z = 0.0 )
: Point2d( x, y ), _z( z ) {};
float z() { return _z; }
void z( float newZ ) { _z = newZ; }
void operator+=( const Point2d& rhs ) {
Point2d::operator+=( rhs );
_z += rhs.z();
}
// ... more members
protected:
float _z;
};
Although the syntax of the class's declaration has not changed, everything about it is now different: The two z() member functions and the operator+=() operator are virtual instances. Each Point3d class object contains an additional vptr member object (the instance inherited from Point2d). There is also a Point3d virtual table. The invocation of each member function made virtual is also more complex (this is covered in Chapter 4).
Placing the vptr at the start of the class is more efficient in supporting some virtual function invocations through pointers to class members under multiple inheritance (see Section 4.4). Otherwise, not only must the offset to the start of the class be made available at runtime, but also the offset to the location of the vptr of that class must be made available. The trade-off, however, is a loss in C language interoperability.
? Multiple Inheritance:
Single inheritance provides a form of "natural" polymorphism regarding the conversion between base and derived types within the inheritance hierarchy. Look at Figures 3.1(b), 3.2(a), or 3.3, where you can see that the base and derived class objects both begin at the same address. They differ in that the derived object extends the length of its nonstatic data members. The assignment, such as
Point3d p3d;
Point2d *p = &p3d;
of the derived class object to a pointer or reference to the base class (regardless of the depth of the inheritance hierarchy) requires no compiler intervention or modification of the address. Instead, it happens "naturally," and in that sense, it provides optimal runtime efficiency.
From Figure 3.2(b), note that placing the vptr at the beginning of the class object breaks the natural polymorphism of single inheritance in the special case of a base class without virtual functions and a derived class with them. The conversion of the derived object to the base in this case requires the intervention of the compiler in order to adjust the address being assigned by the size of the vptr. Under both multiple and virtual inheritances, the need for compiler intervention is considerably more pronounced.
Multiple inheritance is neither as well behaved nor as easily modeled as single inheritance. The complexity of multiple inheritance lies in the "unnatural" relationship of the derived class with its second and subsequent base class subobjects. Consider, for example, the following multiply derived class, Vertex2d:
class Point2d {
public:
// ...
protected:
float _x, _y;
};
class Vertex {
public:
// ...
protected:
Vertex *next;
};
class Vertex2d :
public Point2d, public Vertex {
public:
//...
protected:
float mumble;
};
The problem of multiple inheritance primarily affects conversions between the derived and second or subsequent base class objects, either directly
extern void mumble( const Vertex& );
Vertex3d v;
...
// conversion of a Vertex3d to Vertex is ``unnatural''
mumble( v );
or through support for the virtual function mechanism. The problems with supporting virtual function invocation are discussed in Section 4.2.
The assignment of the address of a multiply derived object to a pointer of its leftmost (that is, first) base class is the same as that for single inheritance, since both point to the same beginning address. The cost is simply the assignment of that address (Figure 3.4 shows the multiple inheritance layout). The assignment of the address of a second or subsequent base class, however, requires that that address be modified by the addition (or subtraction in the case of a downcast) of the size of the intervening base class subobject(s).
What about access of a data member of a second or subsequent base class? Is there an additional cost? No. The member's location is fixed at compile time. Hence its access is a simple offset the same as under single inheritance regardless of whether it is a pointer, reference, or object through which the member is being accessed.
? Virtual Inheritance:
A semantic side effect of multiple inheritance is the need to support a form of shared subobject inheritance. The classic example of this is the original iostream library implementation:
//pre-standard iostream implementation
class ios { ... };
class istream : public ios { ... };
class ostream : public ios { ... };
class iostream :
public istream, public ostream { ... };
Both the istream and ostream classes contain an ios subobject. In the layout of iostream, however, we need only a single ios subobject. The language level solution is the introduction of virtual inheritance:
class ios { ... };
class istream : public virtual ios { ... };
class ostream : public virtual ios { ... };
class iostream :
public istream, public ostream { ... };
The general implementation solution is as follows. A class containing one or more virtual base class subobjects, such as istream, is divided into two regions: an invariant region and a shared region. Data within the invariant region remains at a fixed offset from the start of the object regardless of subsequent derivations. So members within the invariant region can be accessed directly. The shared region represents the virtual base class subobjects. The location of data within the shared region fluctuates with each derivation. So members within the shared region need to be accessed indirectly. What has varied among implementations is the method of indirect access. The following example illustrates the three predominant strategies. Here is the data portion of a virtual Vertex3d inheritance hierarchy:
class Point2d {
public:
...
protected:
float _x, _y;
};
class Vertex : public virtual Point2d {
public:
...
protected:
Vertex *next;
};
class Point3d : public virtual Point2d {
public:
...
protected:
float _z;
};
class Vertex3d :
public Point3d, public Vertex {
public:
...
protected:
float mumble;
};
The general layout strategy is to first lay down the invariant region of the derived class and then build up the shared region.
However, one problem remains: How is the implementation to gain access to the shared region of the class? In the original cfront implementation, a pointer to each virtual base class is inserted within each derived class object. Access of the inherited virtual base class members is achieved indirectly through the associated pointer. For example, if we have the following Point3d operator:
void
Point3d::
operator+=( const Point3d &rhs )
{
_x += rhs._x;
_y += rhs._y;
_z += rhs._z;
};
under the cfront strategy, this is transformed internally into
// Pseudo C++ Code
__vbcPoint2d->_x += rhs.__vbcPoint2d->_x;
__vbcPoint2d->_y += rhs.__vbcPoint2d->_y;
_z += rhs._z;
A conversion between the derived and base class instances, such as
Vertex *pv = pv3d;
under the cfront implementation model becomes
// Pseudo C++ code
Vertex *pv = pv3d ? pv3d->__vbcPoint2d : 0;
3.4 Inheritance and the Data Member
Under the C++ inheritance model, a derived class object is represented as the concatenation of its members with those of its base class(es). The actual ordering of the derived and base class parts is left unspecified by the Standard. In theory, a compiler is free to place either the base or the derived part first in the derived class object. In practice, the base class members always appear first, except in the case of a virtual base class. (In general, the handling of a virtual base class is an exception to all generalities, even, of course, this one.)
Given this inheritance model, one can ask: What is the difference in providing two abstract data types for the representation of two- and three-dimensional points, such as
// supporting abstract data types
class Point2d {
public:
// constructor(s)
// operations
// access functions
private:
float x, y;
};
class Point3d {
public:
// constructor(s)
// operations
// access functions
private:
float x, y, z;
};
and providing a two- or three-level hierarchy in which each additional dimension is a class derived from the lower dimension? In the following subsections, the effects of single inheritance without the support of virtual functions, single inheritance with virtual functions, multiple inheritance, and virtual inheritance are examined. Figure 3.1(a) pictures the layout of Point2d and Point3d objects. (In the absence of virtual functions, they are equivalent to C struct declarations.)
Figure 3.1(a). Data Layout: Independent Structs
Inheritance without Polymorphism
Imagine that the programmer wishes to share an implementation but continue to use type-specific instances of either the two- or three-dimensional point. One design strategy is to derive Point3d from our Point2d class, with Point 3d inheriting all the operations and maintenance of the x- and y-coordinates. The effect is to localize and share data and the operations upon that data among two or more related abstractions. In general, concrete inheritance adds no space or access-time overhead to the representation.
class Point2d {
public:
Point2d( float x = 0.0, float y = 0.0 )
: _x( x ), _y( y ) {};
float x() { return _x; }
float y() { return _y; }
void x( float newX ) { _x = newX; }
void y( float newY ) { _y = newY; }
void operator+=( const Point2d& rhs ) {
_x += rhs.x();
_y += rhs.y();
}
// ... more members
protected:
float _x, _y;
};
// inheritance from concrete class
class Point3d : public Point2d {
public:
Point3d( float x = 0.0, float y = 0.0, float z = 0.0 )
: Point2d( x, y ), _z( z ) {};
float z() { return _z; }
void z( float newZ ) { _z = newZ; }
void operator+=( const Point3d& rhs ) {
Point2d::operator+=( rhs );
_z += rhs.z();
}
// ... more members
protected:
float _z;
};
The benefit of this design strategy is the localization of the code to manage the x- and y-coordinates. In addition, the design clearly indicates the tight coupling of the two abstractions. The declaration and use of both Point2d and Point3d class objects do not change from when the two classes were independent, so clients of these abstractions need not be aware of whether the objects are independent class types or related through inheritance. Figure 3.1(b) shows the layout of the Point2d and Point3d inheritance layout without the declaration of a virtual interface.
Figure 3.1(b). Data Layout: Single Inheritance without Virtual Functions
What are the possible pitfalls of transforming two independent classes into a type/subtype relationship through inheritance? A naive design might, in fact, double the number of function calls to perform the same operations. That is, say the constructor or operator+=() in our example were not made inline (or the compiler could not for some reason support the inlining of the member functions). The initialization or addition of a Point3d object would be the cost of the partial Point2d and Point3d instances. In general, choosing candidate functions for inlining is an important, if unglamorous, aspect of class design. Confirming that they are in fact inlined is necessary before final release of the implementation.
A second possible pitfall in factoring a class into a two-level or deeper hierarchy is a possible bloating of the space necessary to represent the abstraction as a class hierarchy. The issue is the language guarantee of the integrity of the base class subobject within the derived class. It's slightly subtle. A walk-through of an example might best explain it. Let's begin with a concrete class:
class Concrete {
public:
// ...
private:
int val;
char c1;
char c2;
char c3;
};
On a 32-bit machine, the size of each Concrete class object is going to be 8 bytes, broken down as follows:
4 bytes for val
1 byte each for c1, c2, and c3
1 byte for the alignment of the class on a word boundary
Say, after some analysis, we decide that a more logical representation splits Concrete into a three-level inheritance hierarchy as follows:
class Concrete1 {
public:
// ...
protected:
int val;
char bit1;
};
class Concrete2 : public Concrete1 {
public:
// ...
protected:
char bit2;
};
class Concrete3 : public Concrete2 {
public:
// ...
protected:
char bit3;
};
From a design standpoint, this representation may make more sense. From an implementation standpoint, however, we may be distressed to find that a Concrete3 class object now has a size of 16 bytes—double its previous size.
What's going on? Recall that the issue is the integrity of the base class subobject within the derived class. Let's walk through the layout of the inheritance hierarchy to see what is going on.
The Concrete1 class contains the two members—val and bit1—that together take up 5 bytes. The size of a Concrete1 class object, however, is 8 bytes: the 5 bytes of actual size plus 3 bytes of padding to align the object on a machine word boundary. That's as true in C as it is in C++; generally, alignment constraints are determined by the underlying processor.
Nothing necessarily to complain about so far. It's the layout of the derived class that typically drives the unwary programmer into fits of either perplexity or angry indignation. Concrete2 adds a single nonstatic data member, bit2, of type char. Our unwary programmer expects it to be packed into the base Concrete1 representation, taking up one of the bytes otherwise wasted as alignment padding. This layout strategy makes the Concrete2 class object also of size 8 bytes, with 2 bytes of padding.
The layout of the Concrete2 class, however, instead preserves the 3 bytes of padding within the Concrete1 base class subobject. The bit2 member is set down after that, followed by an additional 3 bytes of padding. The size of a Concrete2 class object is 12 bytes, not 8, with 6 bytes wasted for padding. The same layout algorithm results in a Concrete3 class object's being 16 bytes, 9 of which are wasted on padding.
"That's stupid," is the unwary programmer's judgment, which more than one has chosen to share with me over e-mail, on the phone, and in per-son. Do you see why the language behaves as it does?
Let's declare the following set of pointers:
Concrete2 *pc2;
Concrete1 *pc1_1, *pc2_2;
Both pc1_1 and pc2_2 can address objects of either three classes. The following assignment
*pc1_1 = *pc2_2;
should perform a default memberwise copy of the Concrete1 portion of the object addressed. If pc1_1 addresses a Concrete2 or Concrete3 object, that should not be of consequence to the assignment of its Concrete1 subobject.
However, if the language were to pack the derived class members Concrete2::bit2 or Concrete3::bit3 into the Concrete1 subobject, these language semantics could not be preserved. An assignment such as
pc1_1 = pc2;
// oops: derived class subobject is overridden
// its bit2 member now has an undefined value
*pc1_1 = *pc2_2;
would overwrite the values of the packed inherited members. It would be an enormous effort on the user's part to debug this, to say the least.
Adding Polymorphism
If we want to operate on a point independent of whether it is a Point2d or Point3d instance, we need to provide a virtual function interface within our hierarchy. Let's see how things change when we do that:
class Point2d {
public:
Point2d( float x = 0.0, float y = 0.0 )
: _x( x ), _y( y ) {};
// access functions for x & y same as above
// invariant across type: not made virtual
// add placeholders for z ?do nothing ...
virtual float z(){ return 0.0 };
virtual void z( float ) {}
// turn type explicit operations virtual
virtual void
operator+=( const Point2d& rhs ) {
_x += rhs.x(); _y += rhs.y(); }
// ... more members
protected:
float _x, _y;
};
It makes sense to introduce a virtual interface into our design only if we intend to manipulate two- and three-dimensional points polymorphically, that is, to write code such as
void foo( Point2d &p1, Point2d &p2 ) {
// ...
p1 += p2;
// ...
}
where p1 and p2 may be either two- or three-dimensional points. This is not something that any of our previous designs supported. This flexibility, of course, is at the heart of OO programming. Support for this flexibility, however, does introduce a number of space and access-time overheads for our Point2d class:
Introduction of a virtual table associated with Point2d to hold the address of each virtual function it declares. The size of this table in general is the number of virtual functions declared plus an additional one or two slots to support runtime type identification.
Introduction of the vptr within each class object. The vptr provides the runtime link for an object to efficiently find its associated virtual table.
Augmentation of the constructor to initialize the object's vptr to the virtual table of the class. Depending on the aggressiveness of the compiler's optimization, this may mean resetting the vptr within the derived and each base class constructor. (This is discussed in more detail in Chapter 5.)
Augmentation of the destructor to reset the vptr to the associated virtual table of the class. (It is likely to have been set to address the virtual table of the derived class within the destructor of the derived class. Remember, the order of destructor calls is in reverse: derived class and then base class.) An aggressive optimizing compiler can suppress a great many of these assignments.
The impact of these overheads depends on the number and lifetime of the Point2d objects being manipulated and the benefits gained in programming the objects polymorphically. If an application knows its use of point objects is limited to either (but not both) two- or three-dimensional points, the overheads of this design may become unacceptable. [1]
[1] I am not aware of any production system actually making use of a polymorphic Point hierarchy.
Here is our new Point3d derivation:
class Point3d : public Point2d {
public:
Point3d( float x = 0.0, float y = 0.0, float z = 0.0 )
: Point2d( x, y ), _z( z ) {};
float z() { return _z; }
void z( float newZ ) { _z = newZ; }
void operator+=( const Point2d& rhs ) {
Point2d::operator+=( rhs );
_z += rhs.z();
}
// ... more members
protected:
float _z;
};
Although the syntax of the class's declaration has not changed, everything about it is now different: The two z() member functions and the operator+=() operator are virtual instances. Each Point3d class object contains an additional vptr member object (the instance inherited from Point2d). There is also a Point3d virtual table. The invocation of each member function made virtual is also more complex (this is covered in Chapter 4).
One current topic of debate within the C++ compiler community concerns where best to locate the vptr within the class object. In the original cfront implementation, it was placed at the end of the class object in order to support the following inheritance pattern, shown in Figure 3.2(a):
Figure 3.2(a). Vptr Placement and End of Class
struct no_virts {
int d1, d2;
};
class has_virts: public no_virts {
public:
virtual void foo();
// ...
private:
int d3;
};
no_virts *p = new has_virts;
Placing the vptr at the end of the class object preserves the object layout of the base class C struct, thus permitting its use within C code. This inheritance idiom is believed by many to have been more common when C++ was first introduced than currently.
Subsequent to Release 2.0, with its addition of support for multiple inheritance and abstract base classes, and the general rise in popularity of the OO paradigm, some implementations began placing the vptr at the start of the class object. (For example, Martin O'Riordan, who led Microsoft's original C++ compiler effort, persuasively argues for this implementation model.) See Figure 3.2(b) for an illustration.
Figure 3.2(b). Vptr Placement at Front of Class
Placing the vptr at the start of the class is more efficient in supporting some virtual function invocations through pointers to class members under multiple inheritance (see Section 4.4). Otherwise, not only must the offset to the start of the class be made available at runtime, but also the offset to the location of the vptr of that class must be made available. The trade-off, however, is a loss in C language interoperability. How significant a loss? What percentage of programs derive a polymorphic class from a C-lan-guage struct? There are currently no empirical numbers to support either position.
Figure 3.3 shows the Point2d and Point3d inheritance layout with the addition of virtual functions. (Note: The figure shows the vptr placement at the end of the base class.)
Figure 3.3. Data Layout: Single Inheritance with Virtual Inheritance
Multiple Inheritance
Single inheritance provides a form of "natural" polymorphism regarding the conversion between base and derived types within the inheritance hierarchy. Look at Figures 3.1(b), 3.2(a), or 3.3, where you can see that the base and derived class objects both begin at the same address. They differ in that the derived object extends the length of its nonstatic data members. The assignment, such as
Point3d p3d;
Point2d *p = &p3d;
of the derived class object to a pointer or reference to the base class (regardless of the depth of the inheritance hierarchy) requires no compiler intervention or modification of the address. Instead, it happens "naturally," and in that sense, it provides optimal runtime efficiency.
From Figure 3.2(b), note that placing the vptr at the beginning of the class object breaks the natural polymorphism of single inheritance in the special case of a base class without virtual functions and a derived class with them. The conversion of the derived object to the base in this case requires the intervention of the compiler in order to adjust the address being assigned by the size of the vptr. Under both multiple and virtual inheritances, the need for compiler intervention is considerably more pronounced.
Multiple inheritance is neither as well behaved nor as easily modeled as single inheritance. The complexity of multiple inheritance lies in the "unnatural" relationship of the derived class with its second and subsequent base class subobjects. Consider, for example, the following multiply derived class, Vertex3d:
class Point2d {
public:
// ...
protected:
float _x, _y;
};
class Vertex {
public:
// ...
protected:
Vertex *next;
};
class Vertex2d :
public Point2d, public Vertex {
public:
//...
protected:
float mumble;
};
The problem of multiple inheritance primarily affects conversions between the derived and second or subsequent base class objects, either directly
extern void mumble( const Vertex& );
Vertex3d v;
...
// conversion of a Vertex3d to Vertex is ``unnatural''
mumble( v );
or through support for the virtual function mechanism. The problems with supporting virtual function invocation are discussed in Section 4.2.
The assignment of the address of a multiply derived object to a pointer of its leftmost (that is, first) base class is the same as that for single inheritance, since both point to the same beginning address. The cost is simply the assignment of that address (Figure 3.4 shows the multiple inheritance layout). The assignment of the address of a second or subsequent base class, however, requires that that address be modified by the addition (or subtraction in the case of a downcast) of the size of the intervening base class subobject(s). For example, with
Figure 3.4. Data Layout: Multiple Inheritance
Vertex3d v3d;
Vertex *pv;
Point2d *pp;
Point3d *p3d;
the assignment
pv = &v3d;
requires a conversion of the form
// Pseudo C++ Code
pv = (Vertex*)(((char*)&v3d) + sizeof( Point3d ));
whereas the assignments
pp = &v3d;
p3d = &v3d;
both simply require a copying of the address. With
Vertex3d *p3d;
Vertex *pv;
the assignment
pv = p3d;
cannot simply be converted into
// Pseudo C++ Code
pv = (Vertex*)((char*)p3d) + sizeof( Point3d );
since, if p3d were set to 0, pv would end up with the value sizeof(Point3d). So, for pointers, the internal conversion requires a conditional test:
// Pseudo C++ Code
pv = p3d
? (Vertex*)((char*)p3d) + sizeof( Point3d )
: 0;
Conversion of a reference need not defend itself against a possible 0 value, since the reference cannot refer to no object.
The Standard does not require a specific ordering of the Point3d and Vertex base classes of Vertex3d. The original cfront implementation always placed them in the order of declaration. A Vertex3d object under cfront, therefore, consisted of the Point3d subobject (which itself consisted of a Point2d subobject), followed by the Vertex subobject and finally by the Vertex3d part. In practice, this is still how all implementations lay out the multiple base classes (with the exception of virtual inheritance).
An optimization under some compilers, however, such as the MetaWare compiler, switch the order of multiple base classes if the second (or subsequent) base class declares a virtual function and the first does not. This shuffling of the base class order saves the generation of an additional vptr within the derived class object. There is no universal agreement among implementations about the importance of this optimization, and use of this optimization is not (at least currently) widespread.
What about access of a data member of a second or subsequent base class? Is there an additional cost? No. The member's location is fixed at compile time. Hence its access is a simple offset the same as under single inheritance regardless of whether it is a pointer, reference, or object through which the member is being accessed.
Virtual Inheritance
A semantic side effect of multiple inheritance is the need to support a form of shared subobject inheritance. The classic example of this is the original iostream library implementation:
//pre-standard iostream implementation
class ios { ... };
class istream : public ios { ... };
class ostream : public ios { ... };
class iostream :
public istream, public ostream { ... };
Both the istream and ostream classes contain an ios subobject. In the layout of iostream, however, we need only a single ios subobject. The language level solution is the introduction of virtual inheritance:
class ios { ... };
class istream : public virtual ios { ... };
class ostream : public virtual ios { ... };
class iostream :
public istream, public ostream { ... };
As complicated as the semantics of virtual inheritance may seem, its support within the compiler has proven even more complicated. In our iostream example, the implementational challenge is to find a reasonably efficient method of collapsing the two instances of an ios subobject maintained by the istream and ostream classes into a single instance maintained by the iostream class, while still preserving the polymorphic assignment between pointers (and references) of base and derived class objects.
The general implementation solution is as follows. A class containing one or more virtual base class subobjects, such as istream, is divided into two regions: an invariant region and a shared region. Data within the invariant region remains at a fixed offset from the start of the object regardless of subsequent derivations. So members within the invariant region can be accessed directly. The shared region represents the virtual base class subobjects. The location of data within the shared region fluctuates with each derivation. So members within the shared region need to be accessed indirectly. What has varied among implementations is the method of indirect access. The following example illustrates the three predominant strategies. Here is the data portion of a virtual Vertex3d inheritance hierarchy: [2]
[2] This hierarchy is suggested by [POKOR94], an excellent 3D Graphics textbook using C++.
class Point2d {
public:
...
protected:
float _x, _y;
};
class Vertex : public virtual Point2d {
public:
...
protected:
Vertex *next;
};
class Point3d : public virtual Point2d {
public:
...
protected:
float _z;
};
class Vertex3d :
public Point3d, public Vertex {
public:
...
protected:
float mumble;
};
The general layout strategy is to first lay down the invariant region of the derived class and then build up the shared region.
However, one problem remains: How is the implementation to gain access to the shared region of the class? In the original cfront implementation, a pointer to each virtual base class is inserted within each derived class object. Access of the inherited virtual base class members is achieved indirectly through the associated pointer. For example, if we have the following Point3d operator:
void
Point3d::
operator+=( const Point3d &rhs )
{
_x += rhs._x;
_y += rhs._y;
_z += rhs._z;
};
under the cfront strategy, this is transformed internally into
// Pseudo C++ Code
__vbcPoint2d->_x += rhs.__vbcPoint2d->_x;
__vbcPoint2d->_y += rhs.__vbcPoint2d->_y;
_z += rhs._z;
A conversion between the derived and base class instances, such as
Vertex *pv = pv3d;
under the cfront implementation model becomes
// Pseudo C++ code
Vertex *pv = pv3d ? pv3d->__vbcPoint2d : 0;
There are two primary weaknesses with this implementation model:
(1). An object of the class carries an additional pointer for each virtual base class. Ideally, we want a constant overhead for the class object that is independent of the number of virtual base classes within its inheritance hierarchy. Think of how you might solve this.
(2). As the virtual inheritance chain lengthens, the level of indirection increases to that depth. This means that three levels of virtual derivation requires indirection through three virtual base class pointers. Ideally, we want a constant access time regardless of the depth of the virtual derivation.
MetaWare and other compilers still using cfront's original implementation model solve the second problem by promoting (by copying) all nested virtual base class pointers into the derived class object. This solves the constant access time problem, although at the expense of duplicating the nested virtual base class pointers. MetaWare provides a compile-time switch to allow the programmer to choose whether to generate the duplicate pointers. Figure 3.5(a) illustrates the pointer-to-base-class implementation model.
There are two general solutions to the first problem. Microsoft's compiler introduced the virtual base class table. Each class object with one or more virtual base classes has a pointer to the virtual base class table inserted within it. The actual virtual base class pointers, of course, are placed within the table. Although this solution has been around for many years, I am not aware of any other compiler implementation that employs it. (It may be that Microsoft's patenting of their virtual function implementation effectively prohibits its use.)
The second solution, and the one preferred by Bjarne (at least while I was working on the Foundation project with him), is to place not the address but the offset of the virtual base class within the virtual function table. (Figure 3.5(b) on page 100 shows the base class offset implementation model.) I implemented this in the Foundation research project, interweaving the virtual base class and virtual function entries. In the recent Sun compiler, the virtual function table is indexed by both positive and negative indices. The positive indices, as previously, index into the set of virtual functions; the negative indices retrieve the virtual base class offsets. Under this strategy, the Point3d operator is translated into the following general form (leaving off casts for readability and not showing the more efficient precalculation of the addresses):
// Pseudo C++ Code
(this + __vptr__Point3d[-1])->_x += (&rhs + rhs.__vptr__Point3d[-1])->_x;
(this + __vptr__Point3d[-1])->_y += (&rhs + rhs.__vptr__Point3d[-1])->_y;
_z += rhs._z;
Although the actual access of the inherited member is more expensive under this strategy, the cost of that access is localized to a use of the member. A conversion between the derived and base class instances, such as
Vertex *pv = pv3d;
under this implementation model becomes
// Pseudo C++ code
Vertex *pv = pv3d
? pv3d + pv3d->__vptr__Point3d[-1])
: 0;
Each of these are implementation models; they are not required by the Standard. Each solves the problem of providing access to a shared subobject whose location is likely to fluctuate with each derivation. Because of the overhead and complexity of virtual base class support, each implementation is somewhat different and likely to continue to evolve over time.
Access of an inherited virtual base class member through a nonpolymorphic class object, such as
Point3d origin;
...
origin._x;
can be optimized by an implementation into a direct member access, much as a virtual function call through an object can be resolved at compile time. The object's type cannot change between one program access and the next, so the problem of the fluctuating virtual base class subobject in this case does not hold.
In general, the most efficient use of a virtual base class is that of an abstract virtual base class with no associated data members.抽象基才是王?
1. Static Data Members:
Static data members are literally lifted out of their class, as we saw in Section 1.1 and treated as if each were declared as a global variable (but with visibility limited to the scope of the class).但其可视范围只在cd.
Each member's access permission and class association is maintained without incurring any space or runtime overhead either in the individual class objects or in the static data member itself.
A single instance of each class static data member is stored within the data segment of the program. Each reference to the static member is internally translated to be a direct reference of that single extern instance. For example,
// origin.chunkSize == 250;
Point3d::chunkSize == 250;
// pt->chunkSize == 250;
Point3d::chunkSize == 250;
What if the access of the static data member is through a function call or some other form of expression? For example, if we write
foobar().chunkSize == 250;
what happens to the invocation of foobar()? In the pre-Standard language, one didn't know what would happen: It was left unspecified in the ARM whether foobar() had to be evaluated. In cfront, for example, it was simply discarded. Standard C++ explicitly requires that foobar() be evaluated, although no use is made of its result. A probable translation looks as follows:
// foobar().chunkSize == 250;
// evaluate expression, discarding result
(void) foobar();
Point3d::chunkSize == 250;
Taking the address of a static data member yields an ordinary pointer of its data type, not a pointer to class member, since the static member is not contained within a class object. For example,
&Point3d::chunkSize;
yields an actual memory address of type
const int*
2. Nonstatic Data Members:
Nonstatic data members are stored directly within each class object and cannot be accessed except through an explicit or implicit class object. An implicit class object is present whenever the programmer directly accesses a nonstatic data member within a member function. For example, in the following code:
Point3d
Point3d::translate( const Point3d &pt ) {
x += pt.x;
y += pt.y;
z += pt.z;
}
the seemingly direct access of x, y, and z is actually carried out through an implicit class object represented by the this pointer. Internally, the function is augmented as follows:
// internal augmentation of member function
Point3d
Point3d::translate( Point3d *const this, const Point3d &pt ) {
this->x += pt.x;
this->y += pt.y;
this->z += pt.z;
}
Access of a nonstatic data member requires the addition of the beginning address of the class object with the offset location of the data member. For example, given
origin._y = 0.0;
the address of
&origin._y;
is equivalent to the addition of
&origin + ( &Point3d::_y - 1 );//注意?
(Notice the peculiar "subtract by one" expression applied to the pointer-to-data-member offset value. Offset values yielded by the pointer-to-data-member syntax are always bumped up by one. Doing this permits the compilation system to distinguish between a pointer to data member that is addressing the first member of a class and a pointer to data member that is addressing no member(减一用以让编译系l区分两cL据成员指? 一U是dW一个数据成? 另一U是不对数据成员d). Pointers to data members are discussed in more detail in Section 3.6.)
~译时确? 效率不减.The offset of each nonstatic data member is known at compile time, even if the member belongs to a base class subobject derived through a single or multiple inheritance chain. Access of a nonstatic data member, therefore, is equivalent in performance to that of a C struct member or the member of a nonderived class.
Virtual inheritance introduces an additional level of indirection in the access of its members through a base class subobject. Thus
Point3d *pt3d;
pt3d->_x = 0.0;
performs equivalently if _x is a member of a struct, class, single inheritance hierarchy, or multiple inheritance hierarchy, but it performs somewhat slower if it is a member of a virtual base class. In the next sections, I examine the effect of inheritance on member layout. Before I turn to that, however, recall the question at the beginning of this section: When, if ever, is the access of the coordinate data members, such as
origin.x = 0.0;
pt->x = 0.0; //当面临虚基类?amp;pt->x是不定? ?amp;origin.x则是在编译时定?
ever significantly different when accessed through the object origin or the pointer pt? The answer is the access is significantly different when the Point3d class is a derived class containing a virtual base class within its inheritance hierarchy and the member being accessed, such as x, is an inherited member of that virtual base class. In this case, we cannot say with any certainty which class type pt addresses (and therefore we cannot know at compile time the actual offset location of the member), so the resolution of the access must be delayed until runtime through an additional indirection. This is not the case with the object origin. Its type is that of a Point3d class, and the offset location of even inherited virtual base class members are fixed at compile time. An aggressive compiler can therefore resolve the access of x through origin statically.
class Point3d {
public:
// ...
private:
float x;
static List<Point3d*> *freeList;
float y;
static const int chunkSize = 250;
float z;
};
the nonstatic data members are set down in the order of their declaration(按声明的序) within each class object (any intervening static data members, such as freeList and chunkSize, are ignored). In our example, then, each Point3d object consists of three float members in order: x, y, z. The static data members are stored in the program's data segment independent of individual class objects.
The Standard requires within an access section (the private, public, or protected section of a class declaration) only that the members be set down such that "later members have higher addresses within a class object" (Section 9.2 of the Standard). That is, the members are not required to be set down contiguously.(可以不连l但必须从低到高)
What might intervene between the declared members? Alignment constraints on the type of a succeeding member may require padding. This is true both of C and C++, and in this case, the member layout of the two languages is in current practice the same.(寚w)
虚表指针在哪? Traditionally, it has been placed after all the explicitly declared members of the class. More recently, it has been placed at the beginning of the class object. The Standard, by phrasing the layout requirement as it does, allows the compiler the freedom to insert these internally generated members anywhere, even between those explicitly declared by the programmer.
In practice, multiple access sections are concatenated together into one contiguous block in the order of declaration.~译器帮你同c项合ƈ No overhead is incurred by the access section specifier or the number of access levels. For example, declaring eight members in one access section or eight separate access sections in practice results in the same-sized objects.