条款05: 了解C++默认编写并调用了哪些函数
当你自己没有编写时,编译器会默认为你的类创建defult构造函数、析构函数、拷贝构造函数和copy assignment操作符。
如:
class Empty{};
实际上,相当于
class Empty{
public:
Empty(){...} //defualt构造函数
Empty(const Empty& other){...} //copy构造函数
~Empty(...) //析构函数
Empty& opreate=(const Empty& other){...} //copy assignment函数
};
当你自己写了构造函数后,编译器就不会为你生成defualt 构造函数了。
有两种情况下,编译器会拒绝执行copy assignment操作。
template<class T>
class NamedObject {
public:
NamedObject(std::string& name, const T& value);
...
private:
std::string& nameValue;
const T objectValue;
};
std::string NewDog("PersePhone");
std::string OldDog("Satch");
NameObject<int> p(newDog, 2);
NameObject<int> s(OldDog, 36);
p = s; //此过程编译器会报错
因为nameValue是引用类型,C++不允许引用类型指向其他对象。所以当p.nameValue已经赋值引用对象后,不能更改,所以 p=s 操作便会被编译器拒绝执行。
同样的const成员不能更改,所以p=s操作时尝试更改p.objectValue,编译器也会拒绝执行。
另外,如果base classes将copy assignment声明为private时,编译器为拒绝为derived classes生成copy assignment操作符。
编译器可以暗自为class创建defalut构造函数、copy构造函数、copy assignment操作符以及析构函数
条款06:若不想使用编译器自动生成的函数,就该明确拒绝。
如果不想使用编译器支持copy或赋值等功能,可以将copy构造函数和copy assignment操作符声明为private。
class HomeForSale{
private:
HomeForSale(const HomeForSale& );
HomeForSale& operator=(const HomeForSale&);
};
但是这样写会出现问题,比如在member函数或friend函数中这么做时,会引起不必要的错误。所以更合适的写法是定义一个不可复制到base class,然后继承它,如下:
class Uncopyable{
protected:
Uncopyable();
~Uncopyable();
private:
Uncopyable(const Uncopyable& );
Uncopyable& opreator=(const Uncopyable&);
};
class HomeForSale : private Uncopyable{
};
条款07:为多态基类声明virtual析构函数
如果不这么做,会造成派生类没有被析构,产生不可预估的错误。
#include <iostream>
class Base{
public:
Base(){}
virtual void Hello(){std::cout << "base hello \n";}
~Base(){std::cout << "~Base\n";}
};
class Derived : public Base{
public:
Derived(){}
void Hello() override{std::cout << "derived hello\n";}
~Derived(){std::cout << "~Derived\n";}
};
int main(){
Base* base = new Derived();
base->Hello();
delete base;
return 0;
}
输出
derived hello
~Base
可以看到,派生类没有被析构,如果派生类里需要在析构函数里释放内存,就是造成内存泄漏。
但是不是说所有的类都要把析构函数定义为虚函数,因为虚函数需要维持一个虚表,可能会带来额外的内存使用。
带多态性质的基类应该声明一个virtual析构函数
如果class设计的目的不是作为基类或多态性,就不该声明virtual析构函数
条款08:别让异常逃离析构函数
class Widget{
public:
...
~Widget(){} //假设这里会吐出一个异常
};
void dosomthing(){
std::vector<Widget> v;
... //v 在函数结束时自动销毁
}
这样就会造成vector里的Widget析构时产生多个异常,而C++在两个异常同时存在时会结束执行或产生未知错误。
最好的做法就是把异常交给客户处理,并且在析构函数里上双保险,捕获异常。
class DBConn{
public:
...
void close(){
db.close();
closed = true;
}
~DBConn(){
if(!closed){
try{
db.close();
}catch(...){
//记录异常并关闭程序或吞下异常
}
}
}
private:
DBConnction db;
bool closed;
};
析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获它并处理。
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数执行该操作。
条款09:绝不在构造或析构函数中调用virtual函数
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类
条款10:令operator=返回一个reference to *this
int x,y,z;
x=y=z=10;
为了实现连锁赋值,赋值操作符需要返回一个reference执行操作符的左侧实参。
class Widget{
public:
...
Widget& opreator=(const Widget& rhs){
...
return *this;
}
...
};
令assignment操作符返回一个reference to *this
条款11:在operator= 中处理“自我赋值”
class Bitmap{...};
class Widget{
public:
...
Widget& operator=(const widget& rhs){
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
private:
Bitmap* pb;
};
如果参数rhs等于this,也就是发生了自我赋值时,就会造成pb在第一步就被释放掉了。最终this返回的对象指向了一个空指针,发生未知错误。
可以修改为这样
class Bitmap{...};
class Widget{
public:
...
Widget& operator=(const widget& rhs){
if(this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
private:
Bitmap* pb;
};
如果是自我赋值则直接返回。
或者
class Bitmap{...};
class Widget{
public:
...
Widget& operator=(const widget& rhs){
Bitmap* temp = pb;
pb = new Bitmap(*rhs.pb);
delete temp;
return *this;
}
private:
Bitmap* pb;
};
运用临时变量,先报错原来的指针,重新生成对象后,再删除原来的指针。
再或者使用swap
class Bitmap{...};
class Widget{
public:
...
Widget& operator=(const Widget& rhs){
Widget temp(rhs);
swap(temp); //将*this 数据与temp进行交换
return *this;
}
private:
Bitmap* pb;
};
确保当对象自我赋值时operator=有良好的行为
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确
条款12:复制对象时勿忘其每一个成分
比如:
class Customer{
public:
...
Customer(const Customer& rhs):name(rhs.name){}
Customer& operator= (const Customer& rhs){
name = rhs.name;
return *this;
}
private:
std::string name;
};
class PriorityCustomer : public Customer{
public:
...
PriorityCustomer(const PriorityCustomer& rhs):priority(rhs.priority){}
PriorityCustomer& operator=(const PriorityCustomer& rhs){
priority = rhs.priority;
return *this;
}
};
这样在派生类被调用拷贝时,会忘记复制基类的成员变量,造成错误。
正确的做法是:
class Customer{
public:
...
Customer(const Customer& rhs):name(rhs.name){}
Customer& operator= (const Customer& rhs){
name = rhs.name;
return *this;
}
private:
std::string name;
};
class PriorityCustomer : public Customer{
public:
...
PriorityCustomer(const PriorityCustomer& rhs):
Customer(rhs),
priority(rhs.priority){}
PriorityCustomer& operator=(const PriorityCustomer& rhs){
Customer::operator=(rhs);
priority = rhs.priority;
return *this;
}
};
Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”
不要尝试以某个Copying函数实现另一个Copying函数。应该将共同机能放进第三个成员函数中,并由两个Copying函数共同调用。