三:构造、析构、赋值运算二

条款九:绝不在构造和析构过程中调用虚函数

对于有继承体系的类来说,在派生类对象的基类构造期间,对象的类型是基类而不是派生类,这样的话,基类构造函数中若调用了虚函数,那么调用的虚函数一定是基类的,因为此时类型是基类啊,

到这里就gg了,因为你想要的是调用派生类的虚函数,这个行为不是你想要的

小结:

在构造和析构期间不要调用虚函数,因为这类调用从不下降至派生类

条款十:令operator返回一个指向自己的引用

很简单,为了实现连续赋值

int x, y, z;
x=y=z=15; //都是15

条款十一:在operator=中处理自我赋值

我先来写一份代码处理自我赋值,你看看对不:

//准备工作
class Bitmap{...}; 
class Widget{
pulic:
    Widget& operator=(const Widget& rhs);
private:
    Bitmap* pb;
};

//具体实现:
Widget& Widget::operator=(const Widget& rhs){
    if(this == &rhs) return *this; //证同测试,比较是不是同一地址
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

这里的问题是:如果new失败了,pb就指向被删除的内存了,更好的代码如下,我们只要对上述代码做一点点改动即可,而且可以丢掉证同测试:

Widget& Widget::operator=(const Widget& rhs){
    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrig;
    return *this;
}

丢弃看起来美好的证同测试是为了效率;
与前面代码的区别是new成功了再通过副本pOrig来delete;如果new抛出异常,那么pb还是指向原对象,delete不会被执行,所以不用担心原对象被删除。

还有一种通用的方式叫copy and swap技术,我们可以拷贝一份rhs,再把拷贝的rhs与this所指对象交换,这样也就排除了自我赋值的情况:

Widget& Widget::operator=(const Widget& rhs){
    Widget temp(rhs);
    swap(temp);
    return *this;
}
//或者,通过值传递,本身形参rhs就是实参的副本
Widget& Widget::operator=(Widget rhs){
    swap(rhs);
    return *this;
}

小结:

  • 确保自我赋值有良好的行为。技术包括证同测试,先new成功再通过副本delete、以及copy and swap

条款十二:复制对象时勿忘其每一个成分

我们先来看一下傲娇的编译器:如果你自己实现了拷贝函数(拷贝构造函数和拷贝赋值运算符),编译器在你出错时不会告诉你。

  • 你新增了一个成员变量,如果你没有在拷贝函数中去拷贝它,编译器应该报错,它却不报错,所以你得自己小心
  • 为派生类写拷贝函数要自己调用基类的拷贝函数,编译器不会像构造析构那样帮你调基类函数

小结

拷贝函数应该确保复制对象内所有成员变量以及所有基类成分

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务