高效C++:构造/析构/赋值
阅读原文时间:2023年07月10日阅读:1

了解C++默认提供和调用的函数

  • 编译器会自动为每一个空类创建构造函数、拷贝构造函数、赋值运算符以及析构函数

  • 不要使用编译器自动创建的函数,要杜绝这种情况发生,自己编写这些函数

如果不想使用编译器自动生成的函数,要明确拒绝

  • 编译器默认提供的函数,不仅不能给编程带来便利,而且会给在某种情况下引入难以定位的bug,因此应该明确拒绝

  • 拒绝的方式就是明确声明该函数为私有且不提供函数体

  • 私有表示对权限的控制,防止外部在意想不到的情况下调用

  • 不提供函数体,是让编译器在链接的时候报错,明确提示你注意这个函数

class Uncopyable{
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
}
//作为基类继承即可

为多态的基类声明virtual析构函数

  • 如果一个类中有一个虚函数,那么必须将析构函数也申明成虚函数,主要是防止资源泄露(使得析构从下向上依次执行)

  • 如果涉及的类不用于多态,那么就不要声明虚函数,因为虚函数的调用需要借助虚函数列表,调用的效率没有普通函数高

  • 如果不想一个类被实例化,那么就声明该类的虚函数为纯虚函数,且提供其实现

class AbstractClass
{
public:
virtual ~AbstractClass();
}

AbstractClass::~AbstractClass()
{
//函数实现
//目的是进行资源释放
}

把异常消灭在析构函数中

  • 析构函数绝对不要吐出异常,这会导致资源部分释放的问题,析构函数中的异常一定要try…catch处理

  • 如果客户需要对某个操作做出反应,且有可能抛出异常,那么就需要提供一个普通函数执行该操作,这样设计的好处:

    • 提供普通函数给客户使用,且明确会抛出异常,使得用户有机会对异常做出合理的处理

    • 析构函数中检查资源释放的状态,做最后的处理

class DBConn
{
private:
DBConnect dbc;
bool closed;
public:
void close()
{
dbc.close();
closed = true;
}

~DBConn()  
{  
    if(!closed)  
    {  
        try  
        {  
            dbc.close();  
        } catch(...) {  
            //停止或记录  
        }  
    }  
}  

}
//解决close会跑出异常的可能

绝不在构造函数和析构函数中调用virtual函数

  • 看清楚描述,是构造函数和析构函数中不能调用虚函数;析构函数本身是可以设计成虚函数的,且在某些情况下,必须设计成虚函数

  • 在构造函数未执行完成之前,类的虚函数表未建立

  • 进入析构函数之前,类的虚函数表已经销毁,无法调用

让operator=返回一个reference to *this

好处:便于连续赋值,类似下面这种执行方式:

a = b = c = d //连续赋值

class Widget
{
public:
Widget& operator+=(const Widget& rhs)
{
return *this;
}
}

在operator=中处理自我赋值

  • 确认任何函数操作一个以上的对象,且对象可能重复,那么需要进行自我赋值的校验

  • 进行自我赋值的判断:地址判断、copy-and-swap

  • copy-and-swap:交换两个对象的数据,在交换的过程中,任意一句语句发生异常,只会导致交换函数本身交换不成功,不会导致程序发生其他异常,程序依然可以运行,状态回到没有调用交换之前,唯一遗憾的就是程序的业务逻辑错了。这种函数我们叫异常安全函数

Widget& operator=(const Widget& rhs)
{
if(this == &rhs)
return *this;
// …
return *this;
}

Widget& operator=(const Widget& rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}

Widget& operator=(Widget rhs) //拷贝发生在参数传入的过程中
{
swap(rhs);
return *this;
}

赋值对象时不要忘记其中的每一个成员

  • 代码静态检查时,有一条规则就是需要初始化每一个成员变量

  • 拷贝构造函数和赋值运算符不能相互调用---记住就好,如果有相同的代码可以提取成一个private函数

  • 拷贝构造的时候一定要拷贝自己的每一个成员,包括基类的每一个成员