05 了解c++ 默默编写并调用了哪些函数
- 编译器会暗自为class 创建 default构造函数,copy构造函数,copy assignment 操作符,析构函数
- 当class中有成员变量是reference时,有成员是const时,若编程者不定义自己的copy assignment 操作符,c++会拒绝编译调用copy assignment 操作符的那一行
- 当父类将copy assignment 操作符定义为private则也会出现拒绝编译调用copy assignment 操作符的情况
06 若不想编译器自动生成的函数,应该明确拒绝
-
有些类,不希望其通过拷贝构造以及拷贝操作符的编译,只需要将他声明在private中,并不予实现即可
-
如果定义在自己的private中,若member函数和friend函数试图调用copy 构造或者copy assignment
操作符,是连接器报错,想要在编译阶段报错,可以定义一个父类,将父类的copy构造和copy assignment 操作符定义在private并且继承他。 -
c++11之后的=delete操作应该也可以达到一样的效果
07 为多态基类声明virtual析构
- 带多态性质的父类,应该声明一个virtual析构,或者是class带任何virtual函数,都应该声明一个virtual析构
- 如果类的设计目的不是为了做父类,或者不具备多态性质,则不应该声明为virtual析构
当一个父类指针指向子类对象,在子类对象被销毁时,却是通过一个父类指针销毁的,若这个父类还用于一个非虚析构函数,则会发生未定义行为:通常是父类部分被销毁,而子类部分还在,出现局部销毁的情况造成内存泄露
当一个类有virtual函数,通常说明其想要做为一个父类,因此需要一个virtual函数
当一个class中有virtual函数时,他们就需要携带额外的信息,其内部会有一个虚函数表指针指(vptr)向一个虚函数表,这个虚函数表(vtbl),重要的是这个虚函数表会导致类的体积增加,并且影响移植性,因此无端的声明virtual析构是错误的
- 想有一个抽象类的时候(不能被实体化的类),为其定义了一个纯虚析构,那就是一个抽象类,一个小窍门是声明完纯虚析构后,提供一个定义
class AWOV{ //Abstract w/o virtual
public:
virtual ~AWOV() = 0;
}
AWOV::~AWOV(){};
- 因为析构函数的运作方式是从最底层的子类开始析构,一直往上析构,当析构到AWOV时若没有定义,连接器会报错
08不要让异常逃离析构
- 不要让析构函数突出异常,如果析构函数调用的函数可能抛出异常,那么析构函数应该吞下异常(不让传播),结束程序
- 如果客户需要对于某个执行期间的异常做出反应,那么class应该提供一个普通函数版本,双保险
例如在设计一个类用于管理数据库连接的class时,在其析构函数中调用db.close(),以保证在class被销毁时数据库连接能被关闭,但是此操作就有抛出异常的风险,因此应该在析构函数中对异常进行吞下,并提供一个普通函数版本,供此class的使用者自己调用。
09不要在构造和析构函数中调用virtual函数
- 在构造函数和析构函数中不要调用virtual函数,因为他不会调用其子类重写的no-virtual函数
解决办法是将base class中的virtual函数修改为no-virtual,且接收一个参数,并在子类函数将信息传递给父类函数。例如在子类的构造函数参数列表中,调用父类的带参构造
B::B():A(123){};
10 令operator= 返回一个reference to *this
好处显而易见,可以写成连锁形式
int x,y,z;
x=y=z=15;
- 在实现一个class时应当遵循这个协议
class Widget{
public:
...
Widget& operator=(const Widget& rhs){
...
return *this; //return this的解引用获得this指向的Widget对象,然后当做引用返回
}
}
不仅适用于拷贝复制,还适合于+=,-=,*=,/=等
11 在operator= 中处理“自我赋值”
- 确保对象在自我赋值时,operator= 有好的行为,包括比较对象地址,调整语序,copy and swap技术
class Bitmap{};
class Widget{
...
privte:
Bitmap* pb;
}
- 一份不安全的operator=(自我赋值不安全,异常不安全)
Widget& Weidget::operator=(const Widget& rhs){
delete pb;
pb = new Bitmap(*rhs.bp);
return *this;
}
- 自我赋值安全,异常不安全
Widget& Weidget::operator=(const Widget& rhs){
if(this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.bp);
return *this;
}
- 都安全(调整语序,保证先赋值,再删除)
Widget& Weidget::operator=(const Widget& rhs){
Bitmap* temp = pb; //记住之前的pb,方便delete
pb = new Bitmap(*rhs.pb); //让pb指向rhs的副本
delete temp;
return *this;
}
- 都安全的copy and swap技术
Widget& Weidget::operator=(const Widget& rhs){
Widget temp(rhs);
swap(temp); //交换*this 和temp的数据
return *this;
}
12 复制对象不要忘记每一个成分
- 不要忘记每一个成分的赋值,尤其是base class的部分
- 如果copy构造函数和copy operator= 函数有大量重复地方,可以吧重复部分放入init() 函数中,并放入私有部分。
void log(const string& str) {
cout << str << endl;
}
class Base {
public:
Base(string str):name(str) {};
Base(const Base& rhs) :name(rhs.name) {
log("Base copy construct");
}
Base& operator=(const Base& rhs) {
log("Base copy operator");
name = rhs.name;
return *this;
}
string name;
};
class Driver :public Base {
public:
Driver(string str, int age) :age(age), Base(str) {};
Driver(const Driver& rhs) :Base(rhs), age(rhs.age) { //必须在列表中调用父类的拷贝构造,完成子类对象中父类部分的拷贝
log("Driver copy construct"); //子类的拷贝构造
}
Driver& operator=(const Driver& rhs) { //不要漏掉父类部分,调用父类的copy operator=函数
log("Driver copy operator");
Base::operator=(rhs);
age = rhs.age;
return *this;
}
int age;
};
int main() {
Driver d("abc",12);
Driver d1(d);
return 0;
}