《Effective C++》笔记

一、new和delete

1、operator new和operator delete只适合用来分配单一对象。Arrays所用的内存由operator new[]分配出来,并由operator delete[]归还。

 

2、operator new无法满足某一内存分配需求时,它会不断调用new_handler函数尝试分配内存,当指向new_handler函数指针是null时,operator才会抛出异常。

 

3、placement new和placement delete:

对于 Widget *pw = new Widget;

共有两个函数被调用:一个是用以分配内存的operator new,一个是Widget的default构造函数。

 

总结:

1、new一个对象时,通常包含两步,一是调用operator new用以分配内存,一是调用构造函数。

2、而operator new基本操作是,无法满足某一内存分配需求时,它会不断调用new_handler函数尝试分配内存,当指向new_handler函数指针是null时,operator才会抛出异常。

 

二、构造  析构  赋值运算

1、C++默认函数:构造函数、析构函数、拷贝构造函数、赋值构造函数。

 

2、自己声明了构造函数后,编译器就不再创建default构造函数。

 

3、如果class不含virtual函数,通常表示它并不意图被用作一个base class。

 

4、虚函数的实现?

欲实现virtual函数,对象必须携带某些信息,用来在运行期决定哪个virtual函数被调用。这份信息通常是由一个所谓vptr(virtual table pointer)指针指出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table);每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl。

 

5、为什么需要虚析构函数?

当基类指针指向派生类对象,而目前基类的析构函数是non-virtual的时,派生类对象经由基类指针被删除,结果是未定义的——实际执行时通常是对象的派生部分没有被销毁,于是造成诡异的“局部销毁”对象,形成资源泄露。

 

6、何时需要虚析构函数?

无端的将所有类的析构函数声明为virtual,就像从未声明他们为virtual一样,都是错误的。(注:毕竟虚函数表占内存。)

人们的心得是,只有当类内含至少一个virtual函数时,才为它声明virtual析构函数。(注:或者说,只有或只要是基类,才应当或就应当声明为虚析构函数。

 

7、别让异常逃离析构函数

最好是不要让析构函数吐出异常,如果析构函数吐出异常而且程序没有结束,这会导致不明确的行为。当异常抛出时,本身函数就结束,函数里的后续处理就不会执行,这要是发生在析构函数里,可能因为。异常而导致后续的数据释放没有完成,造成内存泄露。

class Test
{
public:
  Test(){ p = new char[4]; }
  ~Test()
  {
    throw("exception"); //退出析构函数
    delete p; //不会执行
  }
private:
  char *p;
};

 

8、绝不在构造和析构过程中调用virtual函数,base class构造期间virtual函数绝不会下降到derived classes阶层。由于base class构造函数的执行更早于derived class函数,当base class构造函数执行时derived class的成员变量尚未初始化。

 

9、赋值构造函数需要证同测试,即判断是否是自我赋值。

 

三、避免遮掩继承而来的名称

1、

class Base {
private:
	int x;
public:
	virtual void mf1() = 0;
	virtual void mf2();
	void mf3();
	...
};

class Derived: public Base {
public:
	virtual void mf1();
	void mf4();
	...
};

 

假设derived class的mf4的实现部分像这样:

void Derived::mf4() {
	...
	mf2();
	...
}

 

编译器首先查找local作用域,也就是mf4覆盖的作用域,没找到;然后查找class Derived覆盖的作用域,没找到;然后找base class作用域,找到了;如果还没找到,接着找含Base的那个namespace作用域,最后往global作用域找去。

 

2、

class Base {
private:
	int x;
public:
	virtual void mf1() = 0;
	virtual void mf1(int);
	virtual void mf2();
	void mf3();
	void mf3(double);
	...
};

class Derived: public Base {
public:
	virtual void mf1();
	void mf3();
	void mf4();
	...
};

Derived d;
int x;
...
d.mf1();	//没问题,调用Derived::mf1
d.mf1(x);	//错误,因为Derived::mf1遮掩了Base::mf1
d.mf2();	//没问题,调用Base::mf2
d.mf3();	//没问题,调用Derived::mf3
d.mf3(x);	//错误,因为Derived::mf3遮掩了Base::mf3

 

3、要解决2中c++对继承而来的名称的缺省遮掩行为,可以使用using 声明。

class Base {
private:
	int x;
public:
	virtual void mf1() = 0;
	virtual void mf1(int);
	virtual void mf2();
	void mf3();
	void mf3(double);
	...
};

class Derived: public Base {
public:
	using Base::mf1;
	using Base::mf3;
	virtual void mf1();
	void mf3();
	void mf4();
	...
};

Derived d;
int x;
...
d.mf1();	//没问题,调用Derived::mf1
d.mf1(x);	//现在没问题了,调用Base::mf1
d.mf2();	//没问题,调用Base::mf2
d.mf3();	//没问题,调用Derived::mf3
d.mf3(x);	//现在没问题了,调用Base::mf3

 

4、有时候并不想继承base class的所有函数,可以使用转交函数(forwarding function)。

class Base {
private:
	int x;
public:
	virtual void mf1() = 0;
	virtual void mf1(int);
	...
};

class Derived: public Base {
public:
	virtual void mf1() {
		Base::mf1();
	}
	...
};

Derived d;
int x;
...
d.mf1();	//没问题,调用Derived::mf1
d.mf1(x);	//错误,Base::mf1()被遮掩了

 


 

四、纯虚函数和虚函数

1、声明一个纯虚函数(pure virtual function)的目的是为了让derived classes只继承函数接口。

声明一个虚函数(impure virtual function)的目的是为了让derived classes继承该函数的接口和缺省实现。

 

2、注意,纯虚函数提供实现,但是必须在抽象类类外实现(当然也可在派生类中实现)。

 

3、综上,纯虚函数和虚函数的一个重大区别是,含有纯虚函数的类是抽象类,抽象类不能实例化对象。

 

4、纯虚函数(pure virtual)、虚函数(virtual)、非虚函数(non-virtual)的区别是:

是为了继承基类的接口,还是使用基类的缺省实现或者覆盖它,还是说完全使用基类的实现版本。再简单地说,是否要使用基类的实现,还是仅仅使用接口。

 

五、绝不重新定义继承而来的缺省参数值

#include <iostream>
using namespace std;

class Base {
public:
	virtual void mf1(int a = 1) {
		cout << "Base::mf1()" << endl;
		cout << a << endl;
	}
};

class Derived: public Base {
	virtual void mf1(int a = 2) {
		cout << "Derived::mf1()" << endl;
		cout << a << endl;
	}
};

int main() {
	Derived d;
	Base *pb = &d;

	pb->mf1();

	return 0;
}

 

 

输出:

Derived::mf1()
1

 

即虽然调用的是派生类的函数,但是参数仍是基类的!

因为virtual函数是动态绑定的,而缺省参数值确实静态绑定的!所以不要重新定义继承而来的缺省参数值!

 

六、明智而审慎地使用private继承

1、private继承不是is-a关系,而是implemented-in-terms-of(根据某物实现)。

private继承意味着只有实现部分被继承,接口部分应略去。

 

七、明智而审慎的使用多重继承

多重继承中,虚基类是为了避免继承而来的成员变量重复,为此,编译器必须提供若干幕后戏法,其后果是:使用virtual继承的那些classes所产生的对象往往比使用non-virtual继承的兄弟们的体积大,访问virtual base classes的成员变量时,也比访问non-virtual base classes的成员变量速度慢。

 

八、透彻了解inline的里里外外

1、定义在class内的函数通常被隐喻作为inline函数。

 

2、inline是个申请,编译器可以忽略它。大部分编译器拒绝将太过复杂(如循环或递归)的函数inline,而所有对virtual函数的调用也会使inline落空(一个编译期,一个运行期)。

 

3、编译期通常不对“通过函数指针而进行的调用”inline,如:

inline void f(){};

void (*pf)() = f;

f();  //inlined

pf(); //可能不被inlined

 

4、“一些没有实现代码的构造函数貌似是inline的绝佳候选人,但事实并非如此,因为编译器可能会在空白的构造函数中添加一些构造对象等之类的代码”。

posted @ 2013-03-20 16:40  helloweworld  阅读(164)  评论(0编辑  收藏  举报