【无标题】effective c++ 读书笔记(2)

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
publicvirtual ~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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值