条款十六: 在operator=中对所有数据成员赋值

当涉及到继承时,派生类的赋值运算符也必须处理它的基类成员的赋值!看看下面:

class base {
public:
  base(int initialvalue = 0): x(initialvalue) {}

private:
  int x;
};

class derived: public base {
public:
  derived(int initialvalue)
  : base(initialvalue), y(initialvalue) {}

  derived& operator=(const derived& rhs);

private:
  int y;
};

逻辑上说,derived的赋值运算符应该象这样:

// erroneous assignment operator
derived& derived::operator=(const derived& rhs)
{
  if (this == &rhs) return *this;    // 见条款17

  y = rhs.y;                         // 给derived仅有的
                                     // 数据成员赋值

  return *this;                      // 见条款15
}

不幸的是,它是错误的,因为derived对象的base部分的数据成员x在赋值运算符中未受影响。例如,考虑下面的代码段:

void assignmenttester()
{
  derived d1(0);                      // d1.x = 0, d1.y = 0
  derived d2(1);                      // d2.x = 1, d2.y = 1

  d1 = d2;         // d1.x = 0, d1.y = 1!
}

请注意d1的base部分没有被赋值操作改变。

解决这个问题最显然的办法是在derived::operator=中对x赋值。但这不合法,因为x是base的私有成员。所以必须在derived的赋值运算符里显式地对derived的base部分赋值。

也就是这么做:

// 正确的赋值运算符
derived& derived::operator=(const derived& rhs)
{
  if (this == &rhs) return *this;

  base::operator=(rhs);    // 调用this->base::operator=
  y = rhs.y;

  return *this;
}

但如果基类赋值运算符是编译器生成的,有些编译器会拒绝这种对于基类赋值运算符的调用(见条款45)。为了适应这种编译器,必须这样实现derived::operator=:

derived& derived::operator=(const derived& rhs)
{
  if (this == &rhs) return *this;

  static_cast<base&>(*this) = rhs;      // 对*this的base部分
                                        // 调用operator=
  y = rhs.y;

  return *this;
}

这段怪异的代码将*this强制转换为base的引用,然后对其转换结果赋值。这里只是对derived对象的base部分赋值。还要注意的重要一点是,转换的是base对象的引用,而不是base对象本身。如果将*this强制转换为base对象,就要导致调用base的拷贝构造函数,创建出来的新对象(见条款m19)就成为了赋值的目标,而*this保持不变。这不是所想要的结果。

不管采用哪一种方法,在给derived对象的base部分赋值后,紧接着是derived本身的赋值,即对derived的所有数据成员赋值。

另一个经常发生的和继承有关的类似问题是在实现派生类的拷贝构造函数时。看看下面这个构造函数,其代码和上面刚讨论的类似:

class base {
public:
  base(int initialvalue = 0): x(initialvalue) {}
  base(const base& rhs): x(rhs.x) {}

private:
  int x;
};

class derived: public base {
public:
  derived(int initialvalue)
  : base(initialvalue), y(initialvalue) {}

  derived(const derived& rhs)      // 错误的拷贝
  : y(rhs.y) {}                    // 构造函数

private:
  int y;
};

类derived展现了一个在所有c++环境下都会产生的bug:当derived的拷贝创建时,没有拷贝其基类部分。当然,这个derived对象的base部分还是创建了,但它是用base的缺省构造函数创建的,成员x被初始化为0(缺省构造函数的缺省参数值),而没有顾及被拷贝的对象的x值是多少!

为避免这个问题,derived的拷贝构造函数必须保证调用的是base的拷贝构造函数而不是base的缺省构造函数。这很容易做,只要在derived的拷贝构造函数的成员初始化列表里对base指定一个初始化值:

class derived: public base {
public:
  derived(const derived& rhs): base(rhs), y(rhs.y) {}

  ...

};

条款十六: 在operator=中对所有数据成员赋值

时间: 2024-10-01 03:04:25

条款十六: 在operator=中对所有数据成员赋值的相关文章

四十六、android中的Bitmap

四十六.android中的Bitmap: http://www.cnblogs.com/linjiqin/archive/2011/12/28/2304940.html 四十七.实现调用Android手机的拍照功能: http://www.cnblogs.com/linjiqin/archive/2011/12/28/2304970.html

条款11:在operator = 中处理&quot;自我赋值&quot;

条款11:在operator = 中处理"自我赋值" 1.潜在自我赋值 int i = 5; int *x = &i; int *y = &i; *x = *y; 继承类的 class Base{}; class Derived: public Base {}; void DoSomeThing(const Base& base, const Derived &derived) { base = derived; } Derived d; Base *b

Effective C++ 条款11,12 在operator= 中处理&ldquo;自我赋值&rdquo; || 复制对象时不要忘记每一个成分

1.潜在的自我赋值     a[i] = a[j];     *px = *py; 当两个对象来自同一个继承体系时,他们甚至不需要声明为相同类型就可能造成别名. 现在担心的问题是:假如指向同一个对象,当其中一个对象被删,另一个也被删,这会造成不想要的结果. 该怎么办? 比如:   widget& widget:: operator+ (const widget& rhs) {    delete pd;    pd = new bitmap(*rhs.pb);    return *thi

C++类中的static数据成员,static成员函数

C++类中谈到static,我们可以在类中定义static成员,static成员函数!C++primer里面讲过:static成员它不像普通的数据成员,static数据成员独立于该类的任意对象而存在,每个static数据成员是与类关联的对象,并不与该类的对象相关联!这句话可能比较拗口,其实可以这么理解:每个static数据成员可以看成是类的一个对象,而不与该类定义的对象有任何关系!下面我们就来具体看看类中的static数据成员! 谈到数据成员,我们最先想到的应该是怎么去定义一个static数据成

Android IOS WebRTC 音视频开发总结(八十六)-- WebRTC中RTP/RTCP协议实现分析

本文主要介绍WebRTC中的RTP/RTCP协议,作者:weizhenwei ,文章最早发表在编风网,微信ID:befoio 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID:blackerteam 或 webrtcorgcn). 一 前言 RTP/RTCP协议是流媒体通信的基石.RTP协议定义流媒体数据在互联网上传输的数据包格式,而RTCP协议则负责可靠传输.流量控制和拥塞控制等服务质量保证.在WebRTC项目中,RTP/RTCP模块作为传输模块的一部分,负责对发送端

C++中的静态数据成员的作用与好处

静态成员如果有n个同类的对象,那么每一个对象都分别有自己的数据成员,不同对象的数据成员各自有值,互不相干.但是有时人们希望有某一个或几个数据成员为所有对象所共有.这样可以实现数据共享. 在前面介绍过全局变量能够实现数据共享. 如果在一个程序文件中有多个函数,在每一个函数中都可以改变全局变量的值,全局变量的值为各函数共享.但是用全局变量的安全性得不到保证,由于在各处都可以自由地修改全局变量的值,很有可能偶一失误,全局变量的值就被修改,导致程序的失败.因此在实际工作中很少使用全局变量. 如果想在同类

Android笔记(二十六) Android中的广播——BroadcastReceiver

什么是广播? 为了方便进行系统级别的消息通知,Android有一套类似广播的消息机制,每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收自己所关心的广播内容,这些广播可能是来自于系统,也可能是来自于其他程序. 广播可以分为两种类型:有序广播和标准广播 标准广播是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因为它们之间没有任何先后顺序可言,这种广播的效率会比较高,但同时也意味着它是无法被截断的 如上图所示,每个人都代表一个广播接收器

前端编程提高之旅(十六)————jquery中的动画

    上一篇文章对jquery中的事件做了总结,这篇文章主要对jquery中的动画做一下总结归类.最近微信端分享中,有很多页面交互及动画做的非常受欢迎,非常符合移动端体验.看似花哨的动画从本质上都脱离不了编写动画的基本方法.乐帝将jquery动画部分内容,做了一个简单的归类.     如下图:     如上图所示,无论多复杂的动画,从实现上都采用这些最底层的动画方法.本篇将从动画方法和与动画状态有关的方法讲起.    一.动画方法    1.同时改变高.宽.不透明度方法    这里涉及show

[笔记]C#基础入门(十六)——C#中if...else条件结构

前一节我们学习了 if 条件结构.条件结构可以有2个分支,比如下面的流程图,判断一个整数是奇数还是偶数: 这个流程图从C#实现如下,其中,条件为 true 时执行的分支写在 if() 后面的{}中:条件为 false 时执行的分支写在 else 后面的{}中. namespace Test { class Program { static void Main(string[] args) { int num = 20;//待判断的数字 if (num % 2== 0)//条件,bool类型 {/