当涉及到继承时,派生类的赋值运算符也必须处理它的基类成员的赋值!否则,当派生类对象向另一个派生类对象赋值时,只有派生类部分赋值了。看看下面:

class base {
public:
base(int initialvalue = ): 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(); // d1.x = 0, d1.y = 0
derived d2(); // 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=:

(直接对this对象的基类部分赋值,此时调用基类的赋值运算符)

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 = ): 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=中对所有数据成员赋值的更多相关文章

  1. 四十六、android中的Bitmap

    四十六.android中的Bitmap: http://www.cnblogs.com/linjiqin/archive/2011/12/28/2304940.html 四十七.实现调用Android ...

  2. C++中的static数据成员与static成员函数

    本文要点: 1.static成员它不像普通的数据成员,static数据成员独立于该类的任意对象而存在,每个static数据成员是与类关联的对象,并不与该类的对象相关联! aka:每个static数据成 ...

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

    C++类中谈到static,我们可以在类中定义static成员,static成员函数!C++primer里面讲过:static成员它不像普通的数据成员,static数据成员独立于该类的任意对象而存在, ...

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

    本文主要介绍WebRTC中的RTP/RTCP协议,作者:weizhenwei ,文章最早发表在编风网,微信ID:befoio 支持原创,转载必须注明出处,欢迎关注我的微信公众号blacker(微信ID ...

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

    为了方便进行系统级别的消息通知,Android有一套类似广播的消息机制,每个应用程序都可以对自己感兴趣的广播进行注册,这样该程序就只会接收自己所关心的广播内容,这些广播可能是来自于系统,也可能是来自于 ...

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

    1.潜在的自我赋值     a[i] = a[j];     *px = *py; 当两个对象来自同一个继承体系时,他们甚至不需要声明为相同类型就可能造成别名. 现在担心的问题是:假如指向同一个对象, ...

  7. 条款十五: 让operator=返回*this的引用

    c++程序员经常犯的一个错误是让operator=返回void,这好象没什么不合理的,但它妨碍了连续(链式)赋值操作,所以不要这样做. 一般情况下几乎总要遵循operator=输入和返回的都是类对象的 ...

  8. 条款11:在operator=中处理“自我赋值”

    什么是自我赋值,就是 v = v 这种类型的语句,也许很多人都会说鄙视这种写法,但是如下的写法会不会出现呢? 比如:a[i] = a[j];      // 不巧的是i可能和j相等 *px = *py ...

  9. Effective C++ 条款11:在operator=中处理"自我赋值"

    "自我赋值"发生在对象被赋值给自己时: class Widget { ... }; Widget w; ... w = w; // 赋值给自己 a[i] = a[j]; // 潜在 ...

随机推荐

  1. H.264和HEVC分析软件和工具【转】

    一.264分析两大利器:264VISA和Elecard StreamEye Tools 264visa 强力的h264实时分析工具 ,能分析各种场合下的h264资源,适用于h264开发者,学习者.在图 ...

  2. 关于mapState和mapMutations和mapGetters 和mapActions辅助函数的用法及作用(四)-----mapActions

    介绍mapActions辅助函数: Action提交的是Mutation,不能够直接修改state中的状态,而Mutations是可以直接修改state中状态的:Action是支持异步操作的,而Mut ...

  3. 【转】Web 前沿——HTML5 Form Data 对象的使用

    XMLHttpRequest Level 2 添加了一个新的接口——FormData.利用 FormData 对象,我们可以通过 JavaScript 用一些键值对来模拟一系列表单控件,我们还可以使用 ...

  4. http 1.0 http 1.1 http 2.0的区别

    HTTP/1.0 版的主要缺点是,每个TCP连接只能发送一个请求.发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接. http1.1优点: 1.HTTP 1.1 版引入了持久连接( ...

  5. 浅谈JS之text/javascript和application/javascript

    问题描述: JS在IE8以下浏览器运行异常 代码: <script>标签是这样子写的: <script type="application/javascript" ...

  6. nexus 搭建 maven 私服

    nexus 搭建 maven 私服 本机环境 Win 8 JDK 7 Maven 3.2 Nexus 2.11 版本选择 http://www.sonatype.org/nexus/archivedn ...

  7. vs2008如何新建自己工程的环境变量(局部)和 Windows系统(全局). .

    在vs2008的Project->Property设置里经常会看到类似$(IntDir).$(OutDir).$(ProjectName) 的预定义宏.以vc2008为例,有时候我们在引用别的库 ...

  8. thinkphp5入口文件对应模块绑定

    在配置文件config.php中找到或者添加 // 入口自动绑定模块 'auto_bind_module' => true, 修改其属性为true

  9. winfrom 多线程 并发 和定时任务

    static void Main(string[] args) { //多个线程修改同一个值,使用lock锁解决并发 for (int i = 0; i < 4; i++) { Task.Fac ...

  10. 笔试算法题(12):整数的string到int转换 & 两个栈实现队列

    出题:将输入的表示整数的字符串转变为对应的整数值: 分析: 每当右边增加一位,说明之前的sum应该高一个数量级,所以*10.由于这两个实现仅仅考虑正规的.正整数输入,所以需要一个Wrapper函数,其 ...