C++中,函数c 调用父类构造函数机制是什么样的

关于类成员函数的调用机制,奇怪,该怎么解决 - C++当前位置:& &&&关于类成员函数的调用机制,奇怪,该怎么解决关于类成员函数的调用机制,奇怪,该怎么解决&&网友分享于:&&浏览:4次关于类成员函数的调用机制,奇怪请提出以下程序运行的结果.并说明为什么
#include &iostream&
using & namespace &
void & dis(){cout & & &AAA & & &}
int & main()
p-& dis();
return & 1;
} ------解决方案--------------------从软件工程的角度来说,这个程序有问题,p指向了非法地址。但从程序本身运行来讲,没有问题
------解决方案--------------------从析构函数角度来说,析构函数已经执行,它通知系统并不再为这块内存维护类对象的一个数据结构了,此时如果其他线程访问这块内存,系统可以分配给其他线程使用,这个程序能够
正常的执行因为单线程,无其他线程可能马上破坏析构后的对象所占据的内存空间,希望说清楚了
------解决方案--------------------class object如何调用class memmber functions
------------------------------------------
类的成员函数在编译时会被“name mangling(名称重整)”,下面具个例子给楼主说明一下如何通过类对象调用member function(仅仅是个例子,编译器的做法可能大同小异)。
class foo{
void test(int a){
f.test(5);//调用成员函数
上面程序经过编译器整理后可能变成下面样子:
struct foo{
void test_foo(foo *const this, int a){//原来的成员函数现在变成了一个全局函数(或者说是“名字空间域函数”),而且名字被“重整”了,以反映出原来是“class foo”的成员(再次强调一下,不同编译器有不同的重整方法)。而且,增加了一个新的参数 &this &,见下面的调用方式,就明白他的用途了
this-& i =a;//通过this指针访问对象的数据成员
test_foo(&f, 5);//将对象f的地址作为this指针传递给test_foo函数,以便访问数据成员
------解决方案--------------------对这个问题的详细讨论可以看这个帖子
http://community.csdn.net/Expert/topic/.xml?temp=4.
我觉得这是出题者的疏忽,不应该出这种有争议的题目
12345678910
12345678910
12345678910 上一篇:下一篇:文章评论相关解决方案 12345678910 Copyright & &&版权所有用汇编语言解析C/C++函数调用中值传递、指针传递和引用传递的内在机制--《计算机时代》2012年09期
用汇编语言解析C/C++函数调用中值传递、指针传递和引用传递的内在机制
【摘要】:为了让学生通过对汇编语言的学习加深对计算机内部原理的理解,借助汇编语言低级化的特点,详细分析了C/C++函数调用中值传递、指针传递和引用传递的内在实现机制。
【作者单位】:
【关键词】:
【分类号】:TP313;TP311.11【正文快照】:
0引言函数是C/C++程序设计中的一个很重要的知识点。在函数调用的过程中,函数参数的传递方式有三种,分别是值传递、引用传递和指针传递,这三者之间到底有什么区别,往往是很多初学者容易犯迷糊的地方。之所以会出现这种情况,其最终原因是由于他们没有从根本上理解这三种参数传
欢迎:、、)
支持CAJ、PDF文件格式,仅支持PDF格式
【参考文献】
中国期刊全文数据库
火善栋;;[J];计算机时代;2010年07期
【共引文献】
中国期刊全文数据库
火善栋;杨旭东;;[J];重庆三峡学院学报;2012年03期
【相似文献】
中国期刊全文数据库
火善栋;;[J];计算机时代;2010年07期
刘胜利;[J];电脑开发与应用;2001年10期
钟治初;;[J];廊坊师范学院学报(自然科学版);2009年02期
薛志文;;[J];电脑知识与技术;2010年18期
王瀚波;任靖福;;[J];牡丹江教育学院学报;2005年04期
史文集;;[J];科技咨询导报;2007年11期
叶雨生;赵海廷;;[J];软件导刊;2010年09期
王宁;;[J];科技传播;2010年09期
杨战海;郭协潮;;[J];现代电子技术;2007年24期
王丕景;[J];太原理工大学学报(社会科学版);2001年02期
中国重要会议论文全文数据库
刘月华;栾爱东;;[A];2004空调器、电冰箱(柜)及压缩机学术交流会论文集[C];2004年
徐冰;张英涛;方连众;史先俊;;[A];高教科研2006(中册:教学改革)[C];2006年
姚敏;刘长征;;[A];美丽人生 和谐世界——中华医学会第七次全国医学美学与美容学术年会、中华医学会医学美学与美容学分会20周年庆典暨第三届两岸四地美容医学学术论坛论文汇编[C];2010年
黄渊;何涛;李爱波;;[A];全国第4届信号和智能信息处理与应用学术会议论文集[C];2010年
杨海涛;于久恩;马彦恒;;[A];中国仪器仪表学会第三届青年学术会议论文集(下)[C];2001年
张银发;丁震;;[A];中国航空学会信号与信息处理专业全国第八届学术会议论文集[C];2004年
陈建孝;赵芜野;;[A];2005全国计算机程序设计类课程教学研讨会论文集[C];2005年
杨林;王秀荣;张桂红;陈苏红;廖明;;[A];中国畜牧兽医学会畜牧兽医生物技术学分会暨中国免疫学会兽医免疫分会第七次研讨会论文集[C];2008年
邓一芸;康焰;廖雪莲;王波;;[A];第三届重症医学大会论文汇编[C];2009年
梁敏华;黄德天;杨华;;[A];2010中国仪器仪表学术、产业大会(论文集2)[C];2010年
中国重要报纸全文数据库
戴丽丽 通讯员
王峻;[N];石家庄日报;2011年
刘洪亮;[N];文汇报;2009年
高浩荣;[N];新华每日电讯;2000年
北京信息工程学院 张泽虹;[N];中国电脑教育报;2006年
丁星星 通讯员
李俊;[N];湛江日报;2010年
许义景;[N];电子报;2004年
高洪宾;[N];人民法院报;2002年
徐泽龙;[N];中国航空报;2000年
潘玉光;[N];中国企业报;2000年
李德洙;[N];人民日报;2001年
中国博士学位论文全文数据库
项森;[D];中国科学技术大学;2006年
郭宇;[D];中国科学技术大学;2007年
王小兵;[D];西安电子科技大学;2009年
李隆;[D];中国科学技术大学;2008年
曾凡平;[D];中国科学技术大学;2009年
赵云山;[D];北京邮电大学;2012年
刘西洋;[D];西安电子科技大学;2007年
王雪飞;[D];西南大学;2008年
李兆鹏;[D];中国科学技术大学;2008年
马立伟;[D];清华大学;2006年
中国硕士学位论文全文数据库
王启明;[D];西安电子科技大学;2009年
黄彪;[D];广东工业大学;2011年
王松;[D];沈阳工业大学;2006年
田丰;[D];合肥工业大学;2007年
郭存锁;[D];北方工业大学;2008年
熊熠;[D];成都中医药大学;2008年
杨俊;[D];中国科学技术大学;2011年
周正;[D];合肥工业大学;2006年
彭文俊;[D];重庆大学;2008年
韩冬;[D];西南交通大学;2009年
&快捷付款方式
&订购知网充值卡
400-819-9993
《中国学术期刊(光盘版)》电子杂志社有限公司
同方知网数字出版技术股份有限公司
地址:北京清华大学 84-48信箱 大众知识服务
出版物经营许可证 新出发京批字第直0595号
订购热线:400-819-82499
服务热线:010--
在线咨询:
传真:010-
京公网安备75号中国领先的IT技术网站
51CTO旗下网站
深入理解C++中的异常处理机制
例子看起来还是比较简单的,但是用处还是很大的,对于赋值操作符来说,很多情况都是需要重载的.
作者:来源:| 11:25
增强错误恢复能力是提高代码健壮性的最有力的途径之一,C语言中采用的错误处理方法被认为是紧耦合的,函数的使用者必须在非常靠近函数调用的地方编 写错误处理代码,这样会使得其变得笨拙和难以使用。C++中引入了异常处理机制,这是C++的主要特征之一,是考虑问题和处理错误的一种更好的方式。使用 错误处理可以带来一些优点,如下:
错误处理代码的编写不再冗长乏味,并且不再和正常的代码混合在一起,只需要编写希望产生的代码,然后在后面某个单独的区段里编写处理错误的嗲吗。多次调用同一个函数,则只需要某个地方编写一次错误处理代码。
错误不能被忽略,如果一个函数必须向调用者发送一次错误信息。它将抛出一个描述这个错误的对象。
传统的错误处理和异常处理
在讨论异常处理之前,我们先谈谈C语言中的传统错误处理方法,这里列举了如下三种:
在函数中返回错误,函数会设置一个全局的错误状态标志。
使用信号来做信号处理系统,在函数中raise信号,通过signal来设置信号处理函数,这种方式耦合度非常高,而且不同的库产生的信号值可能会发生冲突
使用标准C库中的非局部跳转函数 setjmp和longjmp ,这里使用setjmp和longjmp来演示下如何进行错误处理:
#include&&iostream&&#include&&setjmp.h&&jmp_buf&static_&&&void&do_jmp()&{&&&&&&&&&&&&&&&longjmp(static_buf,10);&}&&int&main()&{&&&&&int&ret&=&0;&&&&&&&&&&if((ret&=&setjmp(static_buf))&==&0)&{&&&&&&&&&&&&&&&&&&do_jmp();&&&&&}&else&{&&&&&&&&&&&&&if&(ret&==&10)&&&&&&&&&&&&&std::cout&&&&&a&little&error&&&&&std::&&&&&}&}&
错误处理方式看起来耦合度不是很高,正常代码和错误处理的代码分离了,处理处理的代码都汇聚在一起了。但是基于这种局部跳转的方式来处理代码,在 C++中却存在很严重的问题,那就是对象不能被析构,局部跳转后不会主动去调用已经实例化对象的析构函数。这将导致内存泄露的问题。下面这个例子充分显示 了这点
#include&&iostream&&#include&&csetjmp&&&using&namespace&&&class&base&{&&&&&public:&&&&&&&&&base()&{&&&&&&&&&&&&&cout&&&&&base&construct&func&call&&&&&&&&&&&&&&}&&&&&&&&&~base()&{&&&&&&&&&&&&&cout&&&&&~base&destruct&func&call&&&&&&&&&&&&&&}&};&&jmp_buf&static_&&void&test_base()&{&&&&&base&b;&&&&&&&&&&longjmp(static_buf,47);&}&&int&main()&{&&&&&if(setjmp(static_buf)&==&0)&{&&&&&&&&&cout&&&&&deal&with&some&thing&&&&&&&&&&&&&&test_base();&&&&&}&else&{&&&&&&&&&cout&&&&&catch&a&error&&&&&&&&&&}&}&
在上面这段代码中,只有base类的构造函数会被调用,当longjmp发生了跳转后,b这个实例将不会被析构掉,但是执行流已经无法回到这里,b 这个实例将不会被析构。这就是局部跳转用在C++中来处理错误的时候带来的一些问题,在C++中异常则不会有这些问题的存在。那么接下来看看如何定义一个 异常,以及如何抛出一个异常和捕获异常吧.
异常的抛出
class&MyError&{&&&&&const&char*&const&&public:&&&&&MyError(const&char*&const&msg&=&0):data(msg)&&&&&{&&&&&&&&&&&&&&}&};&&void&do_error()&{&&&&&throw&MyError(&something&bad&happend&);&}&&int&main()&{&&&&&do_error();&}&
上面的例子中,通过throw抛出了一个异常类的实例,这个异常类,可以是任何一个自定义的类,通过实例化传入的参数可以表明发生的错误信息。其实 异常就是一个带有异常信息的类而已。异常被抛出后,需要被捕获,从而可以从错误中进行恢复,那么接下来看看如何去捕获一个异常吧。在上面这个例子中使用抛 出异常的方式来进行错误处理相比与之前使用局部跳转的实现来说,最大的不同之处就是异常抛出的代码块中,对象会被析构,称之为堆栈反解.
异常的捕获
C++中通过catch关键字来捕获异常,捕获异常后可以对异常进行处理,这个处理的语句块称为异常处理器。下面是一个简单的捕获异常的例子:
try{&&&&&&&&&&throw&string(&this&is&exception&);&}&catch(const&string&&e)&{&&&&&cout&&&&&catch&a&exception&&&&&&e&&&&&}&
catch有点像函数,可以有一个参数,throw抛出的异常对象,将会作为参数传递给匹配到到catch,然后进入异常处理器,上面的代码仅仅是 展示了抛出一种异常的情况,加入try语句块中有可能会抛出多种异常的,那么该如何处理呢,这里是可以接多个catch语句块的,这将导致引入另外一个问 题,那就是如何进行匹配。
异常的匹配
异常的匹配我认为是符合函数参数匹配的原则的,但是又有些不同,函数匹配的时候存在类型转换,但是异常则不然,在匹配过程中不会做类型的转换,下面的例子说明了这个事实:
#include&&iostream&&&using&namespace&&int&main()&{&&&&&try{&&&&&&&&&&throw&'a';&&&&&}catch(int&a)&{&&&&&&&&&cout&&&&&int&&&&&&&&&&}catch(char&c)&{&&&&&&&&&cout&&&&&char&&&&&&&&&&}&}&
上面的代码的输出结果是char,因为抛出的异常类型就是char,所以就匹配到了第二个异常处理器。可以发现在匹配过程中没有发生类型的转换。将 char转换为int。尽管异常处理器不做类型转换,但是基类可以匹配到派生类这个在函数和异常匹配中都是有效的,但是需要注意catch的形参需要是引 用类型或者是指针类型,否则会&导致切割派生类这个问题。
&class&Base{&&&&&public:&&&&&&&&&Base(string&msg):m_msg(msg)&&&&&&&&&{&&&&&&&&&}&&&&&&&&&virtual&void&what(){&&&&&&&&&&&&&cout&&&&m_msg&&&&&&&&&&&&&}&&&&&void&test()&&&&&{&&&&&&&&&cout&&&&&I&am&a&CBase&&&&&&&&&&}&&&&&protected:&&&&&&&&&string&m_&};&&class&CBase&:&public&Base&{&&&&&public:&&&&&&&&&CBase(string&msg):Base(msg)&&&&&&&&&{&&&&&&&&&&}&&&&&&&&&void&what()&&&&&&&&&{&&&&&&&&&&&&cout&&&&&CBase:&&&&&m_msg&&&&&&&&&&&&&}&};&&int&main()&{&&&&&try&{&&&&&&&&&&&&&&&&&&&&&&&throw&CBase(&I&am&a&CBase&exception&);&&&&&&}catch(Base&&e)&{&&&&&&&&&&&e.what();&&&&&}&}&
上面的这段代码可以正常的工作,实际上我们日常编写自己的异常处理函数的时候也是通过继承标准异常来实现字节的自定义异常的,但是如果将 Base&换成Base的话,将会导致对象被切割,例如下面这段代码将会编译出错,因为CBase被切割了,导致CBase中的test函数无法 被调用。
try&{&&&&&&&&&&throw&CBase(&I&am&a&CBase&exception&);&&}catch(Base&e)&{&&&&&e.test();&}&
到此为此,异常的匹配算是说清楚了,总结一下,异常匹配的时候基本上遵循下面几条规则:
异常匹配除了必须要是严格的类型匹配外,还支持下面几个类型转换.
允许非常量到常量的类型转换,也就是说可以抛出一个非常量类型,然后使用catch捕捉对应的常量类型版本
允许从派生类到基类的类型转换
允许数组被转换为数组指针,允许函数被转换为函数指针
假想一种情况,当我要实现一代代码的时候,希望无论抛出什么类型的异常我都可以捕捉到,目前来说我们只能写上一大堆的catch语句捕获所有可能在 代码中出现的异常来解决这个问题,很显然这样处理起来太过繁琐,幸好C++提供了一种可以捕捉任何异常的机制,可以使用下列代码中的语法。
catch(...) {
&&& //异常处理器,这里可以捕捉任何异常,带来的问题就是无法或者异常信息
如果你要实现一个函数库,你捕捉了你的函数库中的一些异常,但是你只是记录日志,并不去处理这些异常,处理异常的事情会交给上层调用的代码来处理.对于这样的一个场景C++也提供了支持.
try{&&&&&throw&Exception(&I&am&a&exception&);&&&&}catch(...)&{&&&&&&&&&&throw;&}&
通过在catch语句块中加入一个throw,就可以把当前捕获到的异常重新抛出.在异常抛出的那一节中,我在代码中抛出了一个异常,但是我没有使用任何catch语句来捕获我抛出的这个异常,执行上面的程序会出现下面的结果.
terminate&called&after&throwing&an&instance&of&'MyError'&Aborted&(core&dumped)&
为什么会出现这样的结果呢?,当我们抛出一个异常的时候,异常会随着函数调用关系,一级一级向上抛出,直到被捕获才会停止,如果最终没有被捕获将会 导致调用terminate函数,上面的输出就是自动调用terminate函数导致的,为了保证更大的灵活性,C++提供了set_terminate 函数可以用来设置自己的terminate函数.设置完成后,抛出的异常如果没有被捕获就会被自定义的terminate函数进行处理.下面是一个使用的 例子:
#include&&exception&&#include&&iostream&&#include&&cstdlib&&using&namespace&&&class&MyError&{&&&&&const&char*&const&&public:&&&&&MyError(const&char*&const&msg&=&0):data(msg)&&&&&{&&&&&&&&&&&&&&}&};&&void&do_error()&{&&&&&throw&MyError(&something&bad&happend&);&}&&void&terminator()&{&&&&&cout&&&&&I'll&be&back&&&&&&&&&&exit(0);&}&&int&main()&{&&&&&&&&&&void&(*old_terminate)()&=&set_terminate(terminator);&&&&&do_error();&}&上面的代码会输出I'll&be&back&
到此为此关于异常匹配的我所知道的知识点都已经介绍完毕了,那么接着可以看看下一个话题,异常中的资源清理.
异常中的资源清理
在谈到局部跳转的时候,说到局部调转不会调用对象的析构函数,会导致内存泄露的问题,C++中的异常则不会有这个问题,C++中通过堆栈反解将已经 定义的对象进行析构,但是有一个例外就是构造函数中如果出现了异常,那么这会导致已经分配的资源无法回收,下面是一个构造函数抛出异常的例子:
#include&&iostream&&#include&&string&&using&namespace&&&class&base&{&&&&&public:&&&&&&&&&base()&&&&&&&&&{&&&&&&&&&&&&&cout&&&&&I&start&to&construct&&&&&&&&&&&&&&&&&&if&(count&==&3)&&&&&&&&&&&&&&&&&&throw&string(&I&am&a&error&);&&&&&&&&&&&&&count++;&&&&&&&&&}&&&&&&&&&&~base()&&&&&&&&&{&&&&&&&&&&&&&cout&&&&&I&will&destruct&&&&&&&&&&&&&&&}&&&&&private:&&&&&&&&&static&int&&};&&int&base::count&=&0;&&int&main()&{&&&&&&&&&try{&&&&&&&&&&&&&&base&test[5];&&&&&&&&&&}&catch(...){&&&&&&&&&&&&&&cout&&&&&catch&some&error&&&&&&&&&&&&&&&}&}&上面的代码输出结果是:&I&start&to&construct&I&start&to&construct&I&start&to&construct&I&start&to&construct&I&will&destruct&I&will&destruct&I&will&destruct&catch&some&error&
在上面的代码中构造函数发生了异常,导致对应的析构函数没有执行,因此实际编程过程中应该避免在构造函数中抛出异常,如果没有办法避免,那么一定要 在构造函数中对其进行捕获进行处理.最后介绍一个知识点就是函数try语句块,如果main函数可能会抛出异常该怎么捕获?,如果构造函数中的初始化列表 可能会抛出异常该怎么捕获?下面的两个例子说明了函数try语句块的用法:
#include&&iostream&&&using&namespace&&&int&main()&try&{&&&&&throw&&main&;&}&catch(const&char*&msg)&{&&&&&cout&&&&msg&&&&&&&&&return&1;&}&main函数语句块,可以捕获main函数中抛出的异常.&class&Base&{&&&&&public:&&&&&&&&&Base(int&data,string&str)try:m_int(data),m_string(str)&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&}catch(const&char*&msg)&{&&&&&&&&&&&&&&cout&&&&&catch&a&exception&&&&&msg&&&&&&&&&&&&}&&&&&&private:&&&&&&&&&int&m_&&&&&&&&&string&m_&};&&int&main()&{&&&&&Base&base(1,&zhangyifei&);&}&
上面说了很多都是关于异常的使用,如何定义自己的异常,编写异常是否应该遵循一定的标准,在哪里使用异常,异常是否安全等等一系列的问题,下面会一一讨论的.
C++标准库给我们提供了一系列的标准异常,这些标准异常都是从exception类派生而来,主要分为两大派生类,一类是 logic_error,另一类则是runtime_error这两个类在stdexcept头文件中,前者主要是描述程序中出现的逻辑错误,例如传递了 无效的参数,后者指的是那些无法预料的事件所造成的错误,例如硬件故障或内存耗尽等,这两者都提供了一个参数类型为std::string的构造函数,这 样就可以将异常信息保存起来,然后通过what成员函数得到异常信息.
#include&&stdexcept&&#include&&iostream&&#include&&string&&using&namespace&&&class&MyError:public&runtime_error&{&public:&&&&&MyError(const&string&&msg&=&&&)&:&runtime_error(msg)&{}&&};&&&&int&main()&{&&&&&try&{&&&&&&&&&throw&MyError(&my&message&);&&&&&&&}&&&catch(MyError&&x)&{&&&&&&&&&cout&&&&x.what()&&&&&&&&&&&&}&}&
异常规格说明
假设一个项目中使用了一些第三方的库,那么第三方库中的一些函数可能会抛出异常,但是我们不清楚,那么C++提供了一个语法,将一个函数可能会抛出 的异常列出来,这样我们在编写代码的时候参考函数的异常说明即可,但是C++11中这中异常规格说明的方案已经被取消了,所以我不打算过多介绍,通过一个 例子看看其基本用法即可,重点看看C++11中提供的异常说明方案:
#include&&exception&&#include&&iostream&&#include&&cstdio&&#include&&cstdlib&&using&namespace&&&class&Up{};&class&Fit{};&void&g();&&void&f(int&i)throw(Up,Fit)&{&&&&&switch(i)&{&&&&&&&&&case&1:&throw&Up();&&&&&&&&&case&2:&throw&Fit();&&&&&&&&}&&&&&g();&}&&void&g()&{throw&47;}&&void&my_ternminate()&{&&&&&cout&&&&&I&am&a&ternminate&&&&&&&&&&exit(0);&}&&void&my_unexpected()&{&&&&&cout&&&&&unexpected&exception&thrown&&&&&&&&&&&throw&8;&&&&&&&&&&&&&&&&&&&&&&&&&}&&int&main()&{&set_terminate(my_ternminate);&set_unexpected(my_unexpected);&for(int&i&=&1;i&&=3;i++)&{&&&&&&&&&&&&&&&&&&&&&&&try&{&&&&&&&&&f(i);&&&&&&&&}catch(Up)&{&&&&&&&&&cout&&&&&Up&caught&&&&&&&&&&&&&}catch(Fit)&{&&&&&&&&&cout&&&&&Fit&caught&&&&&&&&&&&&&}catch(bad_exception)&{&&&&&&&&&cout&&&&&bad&exception&&&&&&&&&&&&&}&}&}&
上面的代码说明了异常规格说明的基本语法,以及unexpected函数的作用,以及如何自定义自己的unexpected函数,还讨论了在 unexpected函数中继续抛出异常的情况下,该如何处理抛出的异常.C++11中取消了这种异常规格说明.引入了一个noexcept函数,用于表 明这个函数是否会抛出异常
void recoup(int) noexecpt(true);& //recoup不会抛出异常
void recoup(int) noexecpt(false); //recoup可能会抛出异常
此外还提供了noexecpt用来检测一个函数是否不抛出异常.
异常安全我觉得是一个挺复杂的点,不光光需要实现函数的功能,还要保存函数不会在抛出异常的情况下,出现不一致的状态.这里举一个例子,大家在实现 堆栈的时候经常看到书中的例子都是定义了一个top函数用来获得栈顶元素,还有一个返回值是void的pop函数仅仅只是把栈顶元素弹出,那么为什么没有 一个pop函数可以&即弹出栈顶元素,并且还可以获得栈顶元素呢?
template&typename&T&&T&stack&T&::pop()&{&&&&&if(count&==&0)&&&&&&&&&throw&logic_error(&stack&underflow&);&&&&&else&&&&&&&&&return&data[--count];&}&
如果函数在最后一行抛出了一个异常,那么这导致了函数没有将退栈的元素返回,但是Count已经减1了,所以函数希望得到的栈顶元素丢失了.本质原 因是因为这个函数试图一次做两件事,1.返回值,2.改变堆栈的状态.最好将这两个独立的动作放到两个独立的函数中,遵守内聚设计的原则,每一个函数只做 一件事.我们&再来讨论另外一个异常安全的问题,就是很常见的赋值操作符的写法,如何保证赋值操作是异常安全的.
class&Bitmap&{...};&class&Widget&{&&&&&...&private:&&&&&Bitmap&*&&};&Widget&&Widget::operator=(const&Widget&&rhs)&{&&&&&delete&&&&&&pb&=&new&Bitmap(*rhs.pb);&&&&&return&*this;&}&
上面的代码不具备自我赋值安全性,倘若rhs就是对象本身,那么将会导致*rhs.pb指向一个被删除了的对象.那么就绪改进下.加入证同性测试.
Widget&&Widget::operator=(const&Widget&&rhs)&{&&&&&If(this&==&rhs)&return&*this;&&&&&&delete&&&&&&pb&=&new&Bitmap(*rhs.pb);&&&&&return&*this;&}&
但是现在上面的代码依旧不符合异常安全性,因为如果delete pb执行完成后在执行new Bitmap的时候出现了异常,则会导致最终指向一块被删除的内存.现在只要稍微改变一下,就可以让上面的代码具备异常安全性.
Widget&&Widget::operator=(const&Widget&&rhs)&{&&&&&If(this&==&rhs)&return&*this;&&&&&&Bitmap&*pOrig&=&&&&&&pb&=&new&Bitmap(*rhs.pb);&&&&&&delete&pO&&&&&return&*this;&&&}&
这个例子看起来还是比较简单的,但是用处还是很大的,对于赋值操作符来说,很多情况都是需要重载的.
【编辑推荐】
【责任编辑: TEL:(010)】
大家都在看猜你喜欢
头条头条头条头条外电
24H热文一周话题本月最赞
讲师:1人学习过
讲师:31人学习过
讲师:0人学习过
精选博文论坛热帖下载排行
它从最简单的地方入手,不仅讲述了JavaScript的基础知识,还讲述了JavsScript如何操作CSS、DOM等Ajax基础技术。而关于跨浏览器兼容问题的解...
订阅51CTO邮刊2933人阅读
一点小结(267)
面试宝典(53)
#include &iostream&
class Base{
virtual void f(int a){ cout && &Base::f(int )& && }
virtual void g(){ cout && &Base::g()& && }
virtual void h(){ cout && &Base::h()& && }
void m(){ cout && &Base::m() & && }
void m(int a){ cout && &Base::m(int a) & && }
class Derived:public Base{
void f(){ cout && &Derived::f()& && }
void g(){ cout && &Derived::g()& && }
void m(){ cout && &Derived::m() & && }
void m(int a){ cout && &Derived::m(int a) & && }
void n(){ cout && &Derived::n()& && }
class dd:public Derived{
void f(){ cout && &dd::f()& && }
int main(){
//针对虚函数覆盖的具体情况;虚函数调用机制和非虚函数重名函数调用机制实验
Base *p=new D
typedef void (*Fun)(void);
/*//访问虚函数表
Fun pFun=NULL;
cout && &the address of the V-Table is:
&&& (int *)(&b)&&
cout && &the first function in the V-Table: &&& (int *)*(int *)(&b) &&
pFun=(Fun)* ((int *)*(int *)(&b)); //dd::f()
pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()
pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()
/*//因为Derived从Base继承,所以Derived中也有虚函数,因此Derived类也有自己的V-Table
//证明每个含有虚函数的类都会有虚函数表,且被这个类的所有对象共享,如果子类中有
//和父类虚函数相同,注意必须完全相同的则会覆盖(重写),而不是重载
Fun pFun=NULL;
cout && &the address of the V-Table is:
&&& (int *)(&b)&&
cout && &the first function in the V-Table: &&& (int *)*(int *)(&b) &&
pFun=(Fun)* ((int *)*(int *)(&b)); //Derived::f()
pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()
pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()
/*//这里和Derived类的原因相同,并且g在Derived已经把Base的覆盖了
Fun pFun=NULL;
cout && &the address of the V-Table is:
&&& (int *)(&b)&&
cout && &the first function in the V-Table: &&& (int *)*(int *)(&b) &&
pFun=(Fun)* ((int *)*(int *)(&b)); //dd::f()
pFun=(Fun)* ((int *)*(int *)(&b)+1);//Derived::g()
pFun=(Fun)* ((int *)*(int *)(&b)+2);//Base::h()
C++虚函数表和虚函数调用机制、同名非虚函数调用机制
C++中的虚函数的实现一般是通过虚函数表实现的,尽管C++规范并没有规定具体使用哪种方法来实现虚函数,但大部分的编译器厂商都选择此方法来实现。
类的虚函数表是一块连续的内存,每个内存单元中记录一个JMP指令的地址。
注意:编译器会为每个虚函数的类创建一个虚函数表,该虚函数表将被该类的所有对象共享。类的每个虚成员函数占据函数表中的一个单元,如果类中有N个虚函数,则虚函数表将有N*sizeof(x86)字节的大小。
子类中如果有和父类虚函数冲了函数体定义不同其他完全相同的函数则会覆盖父类的虚函数,而不是重载。
另外如果是虚继承如:class B:public virtual A,则在B中还要保存一个vbptr_B_A虚类指针;每个含有虚函数的类都至少含有一个vPtr,如果子类中只是含有父类的虚函数(可以有virtual标识也可以没有),则和父类共享一个vTable即可,即持有一个vPtr;但是如果子类含有父类中没有的虚函数,则子类需要重新创建一个vTable,保存这个vPtr;同时还要持有父类的vPtr。如果并没有含有另外一个虚函数,但是这个时候B中有了构造函数或者虚函数或者都有,则这个时候也要另外持有一份vtable,即要有自己的一份vptr。这个在计算类占有空间大小的时候很有用。参考《程序员面试宝典》P131
如果父类虚函数没有被覆盖,则这个虚函数是完全没有意义的。
如有以下两个类的继承情况:
&&& virtual
void f(){ cout&&&Base::f()& &&endl; }
&&& virtual
void g(){ cout&&&Base::g()& &&endl; }
&&& virtual
void h(){ cout&&&Base::h()& &&endl; }
m(){ cout &&
&Base::m()& && endl; }
Derived:public
f(){ cout &&
&Derived::f()&&& endl; }
g(){ cout &&
&Derived::g()&&& endl; }
m(){ cout &&
&Derived::m()& && endl; }
n(){ cout &&
&Derived::n()&&& endl; }
第一种情况:我们使用虚函数一般是
Base*p=newDerived;
p-&f(); // Derived::f()
p-&g(); // Derived::g()
p-&h(); // Base::h()
p-&m(); // Base::m()
p-&n();// 编译报错
这是因为:f和g都是虚函数,并且在Derived的对象中都已经将这个函数覆盖了(注意要想覆盖必须是除了函数体定义不同其他的完全相同,否则就不能覆盖而视为不同的函数了);h是虚函数但是没有被覆盖;而n只是Derived类对象的成员函数而不是Base类的成员函数。
第一行代码的意义就是:声明一个对象,并把这个对象的指针强制转换成一个Base类型的,我们知道在创建Derived类对象的时候,其中必然包含了Base基类中的所有内容,在进行指针强制转换后,就只会在继承的Base区域内寻找需要执行的对象或者需要操作的数据。根据上面所描述的,f和g被覆盖了,所以调用的时候就会调用Derived的,h没有被覆盖所以调用Base的h,而n根本就不在这个区域内所以会编译报错:找不到这个成员。
第二种情况:
Derived*p=newDerived;
p-&f(); // Derived::f()
p-&g(); // Derived::g()
p-&h(); // Base::h()
p-&m(); // Derived::m()
p-&n();// Derived::n()
这个时候并没有把Derived类对象的指针做强制转换,这个时候就会在Derived的区域内寻找数据和函数进行执行;这个时候实际上在Derived对象的体内,因为f和g被覆盖了,所以只含有一个f和g而且是Derived的f和g;h没有被覆盖但是h是Base的h;总之对于虚函数的调用是通过查虚函数表来实现的。m在Base和Derived中都有定义但是不是虚函数,所以没有覆盖在这里也不是重载,而是属于两个不同类的函数,在具体调用的时候首先是根据函数名称即标识符来查看调用哪个类中的m函数,这个查找过程是是又一定的名字查找规则的,尤其是在多重继承中体现更明显,在C++编程思想第二卷P374中有具体查找规则描述;确定类了之后再选择函数具体的重载形式,比如如果在Base中m还有一个参数为int的重载形式,而在Derived中没有,但是在调用的时候使用p-&m(1)调用,在这个例子的情形下就会报错,因为根据名称查找规则确定是使用Derived类中的m函数,但是这个类中没有参数为int的重载形式,如果这在Derived中重载了这种形式则肯定没有问题;同样在Derived类中如果不定义m则也没有问题,因为这个时候在名称查找的时候就会确定的是使用Base类中的m函数,而在Base类中恰好有m的int参数的重载形式,所以就不会报错,只是这个时候就会调用Base类中的m函数了。
综上两种情况描述了目前我所见到的类继承中的两大类情形:一种就是虚函数的调用机制;另一种就是同名函数的查找和调用机制。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1061839次
积分:11486
积分:11486
排名:第1076名
原创:231篇
转载:103篇
评论:81条
(4)(5)(1)(2)(59)(40)(21)(22)(32)(36)(35)(26)(9)(1)(15)(1)(13)(12)

我要回帖

更多关于 c 调用父类构造函数 的文章

 

随机推荐