深度探索c++对象模型之类对象的赋值
来源:互联网 发布:python 知乎 编辑:程序博客网 时间:2024/05/12 20:07
在C++中,当我们声明一个类时,如果没有给这个类操作符“=”定义一个函数,那么一般情况下编译器会自动为这个类合成一个默认的copy assignment operator【拷贝赋值操作符】,而这种copy assignment operator的工作模式是bitwise copy,所谓的bitwise copy,意思就是按位施以拷贝【两个类对象除了在内存中的位置不一样,其它的一模一样】。但这种模式是存在一定危险性的,比如,考虑一个虚继承A和B,B继承自A,我们先声明了一个B对象b,然后声明一个A对象a,接着再声明一个A指针对象p;首先,我们让p指向&b,然后再把*p赋值给a,很显然这就是一种错误【在bitwise copy情况下】,因为*p中的所有内容都原封不动照搬给A对象a了,那么也包括*p中的虚表指针,但实际上这个虚表指针是指向B类的,而a中的虚表指针正确情况下应该指向A类!所以编译器是会选择性的给类默认合成bitwise copy式copy assignment operator的,在以下四种情况下,编译器是不会合成出一个bitwise copy模式的copy assignment operator的:
1):一个类中带有成员类对象,而这个内嵌类含有自己的【用户定义】copy assignment operator。
2):一个类的基类有copy assignment operator。
3):当一个类中声明了任何virtual function【虚函数】时。
4):当一个类继承自virtual base class时【无论这个基类有没有copy assignment operator】。
所以,如果我们定义这样一个Point类:
class Point{public:Point(int x=0, int y=0):_x(x),_y(y){};protected:int _x,_y;}
然后我们再写【Point a,b;......b=a;......】时,其中的【b=a;】就是按位照搬【bitwise copy】,这期间并没有调用什么copy assignment operator,而且这种模式效率还很高。
现在,让我们来给Point导入一个copy assignment operator:
inline Point&Point::operator=(const Pont &p){_x=p.x;_y=p.y;return this;}接着,让我们再定义一个Point3d类,该类虚拟继承自Point:
class Point3d:virtual public Point{public:Point3d(int _x=0,int _y=0,int _z=0);protected:int _z;}
在这里,如果我们没有为Point3d定义一个拷贝赋值操作符,编译器就会为Point3d合成一个类似这样的拷贝赋值操作符【伪代码】:
inline Point3d&Point3d::operator=(Point3d* const this, const Point3d &p){//调用基类的函数实体this->Point::operator=(p);//memberwise copy the derived class members_z = p->_z;return *this;}
与constructor不同的是,constructor可以有一个member initialization list,而copy assignment operator却不能有一个member assignment list,因此以下代码是非法的:
//非法代码inline Point3d&Point3d::operator=(const Point3d &p3d):Point(p3d),_z(p3d._z){}
要改成如下形式,才能通过编译:1)【Point::operator=(p3d);】或者【( *(Point *) this ) = p3d;】
好了,现在让我们再创建两个类,Vertex和Vertex3d。其中,Vertex跟Point3d一样,也是虚拟继承自Point;而Vertex3d,则派生自Point3d和Vertex,为了简洁,本文不再赘述这两个类的代码,而是直接看它们的copy assignment operator函数定义:
//Vertex的拷贝赋值操作符inline Vertex&Vertex::operator=(const Vertex& v){this->Point::operator(v);_next = v._next;//这里的_next是Vertex新增的一个指针成员return *this;}
//Vertex3d的拷贝赋值操作符inline Vertex3d&Vertex3d::operator=(const Vertex3d &v3d){this->Point::operator=(v3d);this->Point3d::operator=(v3d);this->Vertex::operator=(v3d);...//其它一些Vertex3d自己成员的拷贝赋值【如果有的话】return *this;}
仔细看上面那两段代码,您会发现问题吗?嗯,那就是在给Vertex3d类的对象们拷贝赋值时,Point的copy assignment operator会被调用三次!那么有什么办法可以压抑限制一下基类的copy assignment operator吗,比如像constructor中传递一个额外的参数?书中给出的答案是不行!但是为什么不行,我当时是左看右看都不明白!我想了想,还是先上传书中的原话吧:
因为自己看不懂的缘故,无奈之下,我去知乎求助,有幸遇到一位好心大牛的回复,总算明白了一点。关键在于那句红横线的话“取copy assignment operator函数地址是合法的”,在彻底理解这句话的背后含义之前,让我们先来回顾一下constructor的额外参数选构法:就是在隐式参数this后面,通过多传递一个叫做“_most_derived”的额外参数,利用它来判定要不要调用基类构造函数。同样的类似方法,为什么不能适用于copy assignment operator呢?让我们看一下上图中作者给出的代码,其中的第二句代码,可以被转换成如下形式:
Point3d& (Point3d::*pmf)(const Point3d&) = Point3d::operator=;如果编译器仿照constructor,给copy assignment operator里面加一个额外的参数,那么在给函数指针pmf声明时参数列表里面就应该加上一个bool型【因为额外参数的类型很可能就是bool型】,所以上图书中的那些代码根本就编译不通过。总之就是如果有额外参数的话,我们在声明一个copy assignment函数指针的时候,参数表里面不应该只有一个“const Point3d&”。C++先驱们很显然不希望用户们为这个参数表而头疼,所以就索性没有仿照constructor的做法。。。
那么是不是可以通过产生一些分化函数【split function】来解决这个问题呢?比如在编译时将copy assignment operator分化为两个,让它们的参数列表不同【有无额外参数】,一个用来对付函数指针问题,一个用来对付高效率拷贝问题。知乎大牛给出的回答是:符合语意!但是编译出来的代码体积将大大增加,也许会得不偿失。
事实上,很多编译器根本就不打算在这上面尝试过多的努力,所以像我们上面的例子,那个Point类的copy assignment operator会被调用多次。大家并不在乎这些效率上的浪费,因为C++标准都说了:我们并没有规定那些代表virtual base class的subobject是否该被“隐喻定义(implicitly define)的copy assignment operator”指派内容一次以上。(C++ Standard,Section 12.8)
本文的最后,首先套用作者的话给大家一个建议:不要在任何virtual base class中声明数据成员!其次,再套用知乎大牛的题外话作为结束:
- 深度探索c++对象模型之类对象的赋值
- 深度探索c++对象模型之类对象数组的黑盒
- 【C++】深度探索C++对象模型之类存储
- PHP5.0对象模型深度探索之类的静态成员
- 深度探索c++对象模型之类全局对象的初始化与析构
- [摘]PHP 5.0对象模型深度探索之类的静态成员
- 深度探索C++对象模型
- 【C++】深度探索C++对象模型之站在对象模型的顶端
- 《深度探索C++对象模型》
- 深度探索C++对象模型
- 深度探索C++对象模型
- 深度探索C++对象模型
- 《深度探索C++对象模型》
- 深度探索C++对象模型
- 深度探索C++对象模型
- 深度探索C++对象模型
- 《深度探索C++对象模型》
- 深度探索c++对象模型
- SQL 数据库 学习 029 查询-12 连接查询 --- 内连接
- struts拦截器小结
- TCP/IP详解:协议(TCP协议在应用层的应用)
- tabu禁忌搜索
- 数据结构比较
- 深度探索c++对象模型之类对象的赋值
- C++ 操作承载网络/虚拟网卡
- 4361: isn
- poj 3468 A Simple Problem with Integers(线段树)
- struts2入门(2)
- 第一章 对象导论 1.13-1.14
- Redis持久化存储(AOF与RDB两种模式)
- iOS--CocoaPods安装及其使用.
- windows下配置mysql主从