IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> [Effective C++]第2章构造/析构/赋值运算 -> 正文阅读

[C++知识库][Effective C++]第2章构造/析构/赋值运算

条款05 了解C++默默编写并调用那些函数

如果自己没有声明,那么C++会为类声明一个构造、一个copy构造、一个析构和一个赋值运算符重载。这些函数都是public inline的。default构造函数和析构函数主要是给编译器一个地方来放置调用基类和non-static成员变量的构造函数和析构函数。编译器产出的析构函数是non-virtual。而copy构造和=运算符重载只是单纯的将源对象的每一个non-static成员变量拷贝到目标对象。

当遇到一下三种情况,必须自定义=赋值重载运算符:

1、类中有常量;2、类中有引用数据;3、基类将=重载设置为private。

遇到以上三种情况,编译器都会拒绝为类生成一个=运算符重载。

template<class T>
class NameObject{
    string& nameValue;
    const T objectValue;
public:
    NameObject(string& name, const T object):nameValue(name),objectValue(object){}
}

很明显,引用数据nameValue和常量objectValue在被初始化之后就不能更改了,所以默认的=重载生成的语法就违反规则了,所以编译器就不会为其生成=重载。而基类将private设置为私有的情况下,若对子类对象进行赋值操作,默认=重载会调用基类的=重载,所以编译器也不允许这种操作。

条款06:若不想使用编译器自动生成的函数,就该明确拒绝

如果想要拒绝类对象的复制行为,有两种做法:

1、将拷贝构造函数和=运算符重载函数设置为private,并且不实现它们;

2、继承一个将拷贝构造函数和=运算符重载函数设置为private的类

class Base{
private:
    Base(const Base&){}
    Base& operator=(cosnt Base&){}
}

class Derived: public Base{        //method 1
private:
}

class Derived{                     //method 2
private:
    Derived(const Derived&){}
    Derived& operator=(cosnt Derived&){}
}

条款07:为多态基类声明virtual析构函数

如果多态基类的析构函数是non-virtual的,会出现一下情况:

int main(){
    Base* bp = new Derived();
    delete bp;
    return 0;
}

如上所述,当delete bp对象的时候,会调用Base的析构函数,而Derived对象没有得到释放。

解决这种问题的方法就是将基类的析构函数声明为virtual,这样dp就会调用Derived对象的析构函数,而Derived对象的析构函数也会调用父类的析构函数,这样所有的内存才得以释放完毕。

当然只有类中含有virtual函数时才需要将析构函数声明为virtual。因为每个带有虚函数的类,其生成的对象都会有一个指向虚函数表的虚函数指针,通过这个指针可以查询到如何调用虚函数。而如果对于没有virtual的类设置virtual析构函数,会造成其对象多了一个虚函数指针,这个指向可能是32位的,也可能是64位的。

条款08:别让异常逃离析构函数

?析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们或结束程序。如下:

DBConn::~DBConn(){
    try{db.close()}
    catch(...){
//        制作运行记录,几下对close的调用失败;
        std::abort();
    }
}

DBConn::~DBConn(){
    try{db.close()}
    catch(...){
//        制作运行记录,几下对close的调用失败;
    }
}

如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数执行该操作。

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

设想在一个父类指针指向子类对象的指针,这个类的构造函数中调用一个virtual函数,而两个类都实现了这个virtual函数,那么父类指针的构造函数会直接调用子类的virtual函数吗?

答案是NO!!!因为父类对象是先于子类对象创建的,而子类对象都没有创建,去哪调用子类的virtual函数?因此父类指针调用的是父类的virtual函数。。。

那么将上述问题的构造函数换成析构函数,父类的析构函数中调用了virtual函数,那么父类指针调用的是父类的virtual还是子类的virtual函数呢?

答案还是父类。因为子类对象的析构函数先于父类对象的析构函数调用,等程序运行到父类对象的析构函数时,子类对象已经没了,因此父类对象只能调用自己的virtual。

条款10:令operator= 返回一个reference to *this

C++中赋值采用右结合律,即x=y=z=15实际上是x=(y=(z=15)),为了实现这种连锁赋值,需要将=运算符重载返回一个*this。这个规则使用于所有的赋值相关运算符重载,如+=等等。

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

?在operator=中如果出现当前对象跟赋值对象相同时,如下所示,如果没有自我检查,那么先把pb删了,在将pb赋给自己,肯定会发生错误。一下有3种方法处理自我赋值。

Method 1是证同测试,但是如果new BitMap(*rhs.pb)发生了异常,那么pb已经没了,无法拯救。Method 2在复制pb所指的东西之后再删除pb,即使new BitMap(*rhs.pb)发生了异常,pb依然存在

//Method 1
Widget& Widget::operator=(const Widget& rhs)
{
    if(this==rhs) return *this;
    delete pb;            //pb是Widget内的一个指针
    pb = new Bitmap(*rhs.pb);
    return *this;
}

//Method 2
Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig = pb;    //pb是Widget内的一个指针
    pb = new Bitmap(*rhs.pb);
    delete pOrig ;            
    return *this;
}

//Method 3
Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);
    swap(temp);            //将*this数据和temp的数据交换            
    return *this;
}

条款12:赋值对象时勿忘其每一个部分

对于copy构造函数和=操作符重载,不仅要考虑其深度拷贝,还需要考虑其父类对象的拷贝,如果不对其父类对象进行拷贝,那么编译器会调用父类对象的default构造函数进行初始化。

解决问题的方法就是调用基类内的适当copying函数。

Derived::Derived(const Derived& rhs):Base(rhs), value(rhs.value)
{}

Derived& Derived::operator=(const Derived& rhs){
    if(*this == rhs) return *this;
    Base::operator=(rhs);
    value = rhs.value;
    return *this;
}

不要让拷贝构造与=运算符重载相互调用。

首先明确:拷贝构造函数的存在意义是通过已有的对象构造新的对象,构造完毕后才有两个对象;重载赋值操作符的意义在于将一个对象的值赋给另一个对象,两个对象都已经构造完毕了。

拷贝构造函数调用重载赋值操作符:把已有对象的值赋给一个构造中的对象,虽然这个对象的内存已经分配好了。(可以接受,但是有可能导致循环调用重载赋值操作符和拷贝构造函数)

重载赋值操作符调用拷贝构造函数:把已有对象复制并赋值给这个对象。——多一个临时对象,而且导致循环调用重载赋值操作符。重载赋值操作符调用拷贝构造函数会导致循环调用重载赋值操作符?

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 12:43:52  更:2022-03-06 12:46:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:45:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码