深入探索C++对象模型-5
虚拟继承下的对象构造:
由于虚拟基类对象在子类中只能保持一个实例,那么,子类构造的时候调用父类的构造函数的时候必须保证虚拟基类对象不能够重复构造。
那么如何保证基类对象的唯一性?
C++规定虚拟基类对象的构造只能是最外层的子类进行构造,浅层次的子类将不会在进行构造,保证了虚拟基类对象的唯一性。
在虚拟继承体系下,子类的构造函数中必须做一个判断,设置一个标准位,用来判断虚拟基类对象是否已经构建,然后将该标志为传递给浅层次的子类,那么虚拟基类将不会再次构造。
例如,编译器会为子类构造函数内部设置标志位
Point3D::Point3D(Point3D *this,bool _most_derived)
{
if(_most_derived!=fales) Point();//如果是最外层子类,构建虚拟基类对象 Point2D(false);
Vertex(false);//将false传入说明其父类不是最外层,将不会构建虚拟基类对象
}
继承体系下的对象构造:
必须首先将父类对象构造再构造子类对象内容。在子类构造函数中调用父类构造函数的方法可以是在成员初始化列表中显示调用构造函数,如果没有在成员初始化列表中进行构造,那么,编译器会在子类构造函数中扩充调用父类默认构造函数进行构造父类对象。
Vptr的深入探索
在前面我们知道,Vptr必须在构造对象的时候进行初始化设置,使它指向正确的类的虚表地址。
那么,在什么时候进行vptr的设置呢?
C++标准规定构造函数中内容执行的顺序:
1、首先调用虚拟基类(若有首先调用虚拟基类构造函数)或基类们的构造函数,构建基类子对象;
2、设置该对象的vptr,使其指向适应的虚拟函数表;
3、执行成员初始化列表中的成员初始化;
4、执行用户程序内容。
因此,当遇到在构造函数或析构函数中调用虚函数问题的时候,答案就会很明确了。
在基类构造函数中调用虚函数,将不会使用多态机制,即不会调用其子类中的虚函数,因为在基类构造的时候,vptr的设置仍指向基类的虚表,而子类还 未完成构造,vptr还未指向子类的虚表,因此,此时不会使用多态机制,仍然调用基类中的虚函数实例。这个是值得我们注意的,同时,对于这个,在《Effective C++》里也有相关的说明!
而在构造函数中使用成员函数,成员函数中调用虚函数时也不适用多态机制。只有在非构造函数中调用虚函数时才会使用多态机制。
同理,析构函数中内容的顺序正好相反:
1、调用子类析构函数中实体,完成用户程序中内容的退栈;
2、析构释放子类中不同于基类的成员;
3、调整设置vptr,使其指向基类相对应的虚表;
4、完成基类的析构。
在基类析构函数中调用虚函数,也不会实现多态机制,因为子类已经析构完毕,vptr指向基类的虚表。
赋值函数的深入探索:
未显示定义的赋值函数,编译器将视情况为类合成赋值函数,条件和合成复制构造函数的相同,只有当复制不适合 bitwise 的时候才会很成默认构造函数。
注意:复制构造函数时进行vptr的设定,而赋值函数不会进行vptr的设置,也就是说当以子类对象赋值给父类对象时,将不会改变父类对象的vptr指向,因为父类对象在构造的嘶吼已经进行了设定。
赋值函数需要进行自我识别:
加上一句,防止自我复制
if(this==&参数对象)return *this;
另外,赋值函数不能使用成员初始化列表,只有构造函数才能使用,这样就会导致,虚拟继承情况下,使用赋值函数复制对象时,会在被赋值的对象中出现多个虚拟基类对象的现象。
例如:
类A,B虚拟继承类base,C继承A和B,那么C的赋值函数就会这样写:
C& operator=(const C& c)
{
if(this==&c)return *this;
A::operator=(c);
B::operator=(c);//导致出现两份虚拟基类对象实例
//C自己的成员的复制
return *this;
}
建议:尽量不要使用赋值函数进行虚拟继承子类对象的复制。
对象数组的构造:
对象数据的构造一般有两种方式:静态和动态
(1)静态分配
以string类为例,string a[10];就是以静态形式构造数据,这样的数组的个数是确定的不能修改的。
像这样的数组怎么进行构造和析构呢?
编译器在构造数组的时候会生成一个使用默认构造函数的数组构造函数arr_new(char *p,sizeof(string),int num,构造函数地址,析构函数地址);
同样也会生成数组析构函数,形式类似。arr_del(char *p,sizeof(string),int num,析构函数地址);
若数组构造中间出现异常,该函数必须保证已构造的对象析构,然后释放内存。
如果数组对象不使用默认构造函数构造对象,必须显示构造,否则,未显示构造的对象将会使用上述函数进行默认构造。
(2)动态分配
使用new表达式进行操作,string *a=new string[10];
new表达式分为两个步骤:首先通过内存分配所用类型大小的空间,然后再该空间上调用相应的构造函数进行构造,上述语句使用默认构造函数。
delete 表达式则释放指针所指的内存(首先析构),大小按照指针类型的大小计算。与数组相对应的为delete[] a;
这样就可能造成一定错误:
当使用基类指针指向一个子类数组,则释放的时候将可能会产生错误
Base *p=new Derived[10];
delete[] p;
我们知道,new出来的数组是根据Derived对象大小*10的内存空间,而delete 则是根据指针类型的大小进行析构和释放内存的,且使用类似与静态分配时的arr_del函数进行析构释放内存,这样调用的就会是基类的析构函数和基类对象 的大小。除了第一个元素,其他元素的析构都会错误的进行。
因此建议:不要使用基类指针指向一个子类数组。
上诉问题根据编译器而不同,微软的编译器可以支持使用基类指针释放子类数组,但是基于cfront的编译器g++将会出现错误,它会将指针类型的大小和析构函数传入它生成的arr_del函数进行析构释放,导致内存错误。
深入探索C++对象模型-5的更多相关文章
- 读书笔记《深度探索c++对象模型》 概述
<深度探索c++对象模型>这本书是我工作一段时间后想更深入了解C++的底层实现知识,如内存布局.模型.内存大小.继承.虚函数表等而阅读的:此外在很多面试或者工作中,对底层的知识的足够了解也 ...
- 柔性数组-读《深度探索C++对象模型》有感 (转载)
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
- 柔性数组-读《深度探索C++对象模型》有感
最近在看<深度探索C++对象模型>,对于Struct的用法中,发现有一些地方值得我们借鉴的地方,特此和大家分享一下,此间内容包含了网上搜集的一些资料,同时感谢提供这些信息的作者. 原文如下 ...
- [读书系列] 深度探索C++对象模型 初读
2012年底-2014年初这段时间主要用C++做手游开发,时隔3年,重新拿起<深度探索C++对象模型>这本书,感觉生疏了很多,如果按前阵子的生疏度来说,现在不借助Visual Studio ...
- 拾遗与填坑《深度探索C++对象模型》3.3节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- 拾遗与填坑《深度探索C++对象模型》3.2节
<深度探索C++对象模型>是一本好书,该书作者也是<C++ Primer>的作者,一位绝对的C++大师.诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书 ...
- 深度探索C++对象模型
深度探索C++对象模型 什么是C++对象模型: 语言中直接支持面向对象程序设计的部分. 对于各个支持的底层实现机制. 抽象性与实际性之间找出平衡点, 需要知识, 经验以及许多思考. 导读 这本书是C+ ...
- 《深度探索C++对象模型》读书笔记(一)
前言 今年中下旬就要找工作了,我计划从现在就开始准备一些面试中会问到的基础知识,包括C++.操作系统.计算机网络.算法和数据结构等.C++就先从这本<深度探索C++对象模型>开始.不同于& ...
- C++的黑科技(深入探索C++对象模型)
周二面了腾讯,之前只投了TST内推,貌似就是TST面试了 其中有一个问题,“如何产生一个不能被继承的类”,这道题我反反复复只想到,将父类的构造函数私有,让子类不能调用,最后归结出一个单例模式,但面试官 ...
- 深入探索C++对象模型(一)
再读<深入探索C++对象模型>笔记. 关于对象 C++在加入封装后(只含有数据成员和普通成员函数)的布局成本增加了多少? 答案是并没有增加布局成本.就像C struct一样,memeber ...
随机推荐
- AJAX编程模板
AJAX一直以来没怎么接触,主要是做JSON数据在服务器和客户端之间传递的时候,被玩坏了,对它莫名的不可爱,最近心理阴影小了,于是又来看看它....... AJAX即“Asynchronous Jav ...
- Android 即时语音聊天工具 开发
使用融云SDK 1. 功能需求分析 1.1 核心功能需求: * 即时通讯 * 文字聊天 * 语音聊天 1.2 辅助功能需求: * 注册.登录 * 好友添加功能 * 好友关系管理 2. 融云即时通讯平台 ...
- [ZHUAN]Flask学习记录之Flask-SQLAlchemy
From: http://www.cnblogs.com/agmcs/p/4445583.html 各种查询方式:http://www.360doc.com/content/12/0608/11/93 ...
- 淘宝API举例子
# -*- coding: utf-8 -*- import urllib2 import urllib import time import md5 secret='xx' app_key='yy' ...
- django笔记1
最近在博客园看来越来越多的关于python的文章,我看到时感觉特别的好,因为我也是一个特别喜欢python这门语言,喜欢python的简洁.干净,简洁而不失强大. 最近在学习django的Model模 ...
- 舵机的PWM控制学习随笔
舵机的控制信号,对于脉宽调制信号的脉宽变换,常用的一种方法是采用调制信号获取有源滤波后的直流电压,但是需要50Hz(周期是20ms)的信号,这对运放器件的选择有较高要求,从电路体积和功耗考虑也不易采用 ...
- shell条件测试
文件状态测试-b filename : 当filename 存在并且是块文件时返回真(返回0)-c filename : 当filename 存在并且是字符文件时返回真-d pathname : 当p ...
- google yeoman
Yeoman是Google的团队和外部贡献者团队合作开发的,他的目标是通过Grunt(一个用于开发任务自动化的命令行工具)和Bower(一个HTML.CSS.Javascript和图片等前端资源的包管 ...
- 人在江湖飘,哪能不挨刀。CENTOS之后,UBUNTU,FEDORA都要安装起来作测试啊
还好,我们有VIRTUAL BOX.
- PPI是什么?如何计算?
PPI,英文全称:pixels per inch,即像素每英寸,也叫像素密度,它是描述在水平的和垂直的方向上,每英寸距离的图像包含的像素(pixel)数目.因此PPI数值越高,即代表显示屏能够以越高的 ...