对象复制语意学 (Object Copy Semantics)(第五章)

5.3 对象复制语意学 (Object Copy Semantics) 当设计一个 class,并以一个 class object指定给 class object时,有三种选择: 1.什么都不做,因此得以实施默认行为. 2.提供一个 explicit copy assignment operator. 3.明确地拒绝一个 class object指定给另一个 class object. 如果要选择第3点,不允许将一个 class object指定给另一个 class object,那么只要将copy assignment operator声明为 private,并且不提供其定义即可.把它设置为 private,就不再允许于任何地点(除了在member functions以及此 class 的friend中)进行赋值(assign)操作.不提供其函数定义,则一旦某个member function或 friend 企图影响一份拷贝,程序在链接时就会失败.一般认为这和链接器的性质有关. 在这一节,验证copy assignment operator的语意,以及它们如何被模塑出来,利用Point class 来帮助讨论:class Point {public:Point(float x= 0.0, float y = 0.0);// … 没有virtual functionprotected:float _x, _y;}; 没有理由需要禁止拷贝一个Point object.因此问题就变成了:默认行为是否足够?如果要支持的只是一个简单的拷贝操作,那么默认行为不但足够而且有效率,没有利用再自己提供一个copy assignment operator. 只有在默认行为导致的语意不安全或者不正确时,才需要设计一个copy assignment operator.默认的memberwise copy行为对Point不安全吗?不正确吗?不,由于坐标都内带数值,所以不会发生"别名话"或"内存泄露".如果自己提供copy assignment operator,程序反倒会执行的比较慢. 如果不对Point供应一个copy assignment operator,而只是依赖默认的memberwise copy,编译器会产生出一个实体吗?这个答案和copy constructor的情况一样:实际上不会!由于此 class 已经有了bitwise copy语意,所以implicit copy assignment operator被视为毫无用处,也根本不会被合成出来. 一个 class 对于默认的copy assignment operator,在以下情况不会表现出bitwise copy语意: 1.当 class 内带一个member object,而其 class 有一个copy assignment operator时. 2.当一个 class 的base class 有一个copy assignment operator时. 3.当一个 class 声明了任何 virtual functions(一定不能够拷贝右端 class object的vptr地址,因为它可能是一个derived class object). 4.当 class 继承自一个 virtual base class(不论此base class 有没有copy operator)时. C++ Standard上说copy assignment operators并不表示bitwise copy semantics是nontrivial.实际上,只有nontrivial instances才会被合成出来. 于是,对于Point class,这样的赋值(assign)操作:Point a, b;a = b; 由bitwise copy完成,把Point b拷贝给Point a,其间并没有copy assignment operator被调用.从语意上或从效率上考虑,这都是所需要的.注意,还是可能提供一个copy constructor,为的是把name return value(NRV)优化打开.copy constructor的出现不应该暗示出也一定要提供一个copy assignment operator. 现在导入一个copy assignment operator,用以说明该operator在继承之下的行为:inline Point &Point::operator=(const Point &p) {_x = p._x;_y = p._y;} 现在派生出一个Point3d class(请注意是虚拟继承):class Point3d : virtual public Point {public:Point3d(float x = 0.0, float y = 0.0, float z = 0.0);protected:float _z;}; 如果没有为Point3d定义一个copy assignment operator,编译器就必须合成一个,合成而得的东西可能看起来像这样:// C++伪代码:被合成的copy assignment operatorinline Point3d &Point3d::operator=(Point3d *const this, const Point3d &p) {// 调用base class的函数实体this->Point::operator=(p);// memberwise copy the derived class members_z = p._z;return *this;} copy assignment operator有一个非正交性情况(nonorthogonal aspect,意指不够理想,不够严谨的情况),那就是它缺乏一个member assignment list.因此不能重写:// C++伪代码,以下性质并不支持inline Point3d &Point3d::operator=(const Point3d &p3d) : Point(p3d), z(p3d._z){} 必须写成以下两种形式,才能调用base class 的copy assignment operator:Point::operator=(p3d); 或(*(Point *)this) = p3d; 缺少copy assignment list,看起来或许只是一件小事,但如果没有它,编译器一般而言就没有办法压抑上一层base class 的copy operators被调用.例如,下面是个Vertex copy operator,其中Vertex也是虚拟继承自Point:// class Vertex : virtual public Pointinline Vertex &Vertex::operator=(const Vertex &v) {this->Point::operator(v);_next = v._next;return *this;} 现在从Point3d和Vertex中派生出Vertex3d,下面是Vertex3d的copy assignment operator:inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) {this->Point::operator=(v);this->Point3d::operator(v);this->Vertex::operator=(v);} 编译器如何能够在Point3d和Vertex的copy assignment operators中压抑Point的copy assignment operators呢?编译器不能够重复传统的constructor解决方案(附加上额外的参数).这是因为,和constructor以及destructor不同的是,"取copy assignment operator地址"的操作是合法的.因此,下面这个例子是合法程序代码(虽然它也推翻了希望把copy assignment operator做的更精巧的企图):typedef Point3d &(Point3d::*pmfPoint3d) (const Point3d &);pmfPoint3d pmf = &Point3d::operator=;(x.*pmf)(x); //看不懂…………………………… 然而无法支持它,仍然需要根据其独特的继承体系,插入任何可能数目的参数给copy assignment operator.这一点在支持由 class object(内带 virtual base class)所组成的数组的配置操作时,也被证明是很有问题的(详见6.2节) 另一个方法是,编译器可能为copy assignment operator产生分化函数(split functions),以支持这个 class 成为most-derived class 或成为中间的base class.如果copy assignment operator被编译器产生的话,那么"slit function解决方案"可说是定义明确.但如果它是被 class 设计者所完成的,那就不算是定义明确.例如,如何分化像下面这样的函数(特别是当init_bases()为 virtual 时):inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) {init_bases(v);} 事实上,copy assignment operator在虚拟继承情况下行为不佳,需要特别小心地设计和说明. 如果使用一个以语言为基础的解决方法,那么应该为copy assignment operator提供一个附加的"member copy list".简单地说,任何解决方案如果是以程序操作为基础,将导致较高的复杂度和较大的错误倾向.一般公认,这是语言的一个弱点,也是应该小心检验程序代码的地方(当使用 virtual base classes时). 有一种方法可以保证most-derived class 会引发 virtual base class subobject的copy行为,那就是在derived class 的copy assignment operator函数实体的最后,明确地调用那个operator,像这样:inline Vertex3d &Vertex3d::operator=(const Vertex3d &v) {this->Point3d::operator=(v);this->Vertex:;operator=(v);// must place this last if your compiler dose not suppress intermediate class invocationsthis->Point::operator=(v);} 这并不能省略subobjects的多重拷贝,但却可以保证语意正确.另一个解决方案要求把 virtual subobjects拷贝到一个分离的函数中,并根据call path条件化调用它. 最好的办法是尽可能不要允许一个 virtual base class 的拷贝操作.甚至有一个奇怪的方法是:不要在任何 virtual base class 中声明数据

版权声明:本文为博主原创文章,未经博主允许不得转载。

,而是他们在同伴们都睡着的时候,一步步艰辛地

对象复制语意学 (Object Copy Semantics)(第五章)

相关文章:

你感兴趣的文章:

标签云: