牛骨文教育服务平台(让学习变的简单)

Effective C++ 29-33

29.避免返回内部数据的句柄。

即使声明一个类的对象为const,不能进行修改,在获得其数据的句柄也就是地址的情况下,还是可以强行修改的。

class A{
public:
	int n;
	A(int x):n(x){}
	operator int*() const;
};
inline A::operator int*()const{
	return const_cast<int*>(&n);
}
int main(){
	const A a(1);
	A& b = const_cast<A&>(a);
	b.n = 2;
	cout<<a.n;

使用const_cast, 再给常对象起一个别名就可以修改这个 本来不能修改的常量对象了。这种方法是无法避免的,常对象注定只是表面上的,最终还是可以修改的,因为人们可以通过各种方式获得其内存,然后强制修改内存上的内容。

不考虑这种残忍的方法,在重载int 操作符时,由于函数体是const,n就成了 const int n,所以&n获得的是一个const int,而返回值应该是一个int,不是常量指针,所以又使用了const_cast,返回了一个指针,这个情况不科学,换另外一种情况A内保存了一个指针 data,指向储存的数据,对于const,表示指针是常量不能改变,但指针指向的内容可以改变,即 data为 int const data ,则data可以作为int * 当作返回值传递给调用者,而调用者即可以随意修改data指向的私有数据。

则对于const对象的的 T * 操作符,如果选择申请一个新的内存来存放数据,并返回这块新数据的指针的话,速度较慢,且内存容易泄漏。而最好的方法应该是使这个指向const对象返回的指针为const指针,这样即安全有快速:

inline A::operator int *const() const{
	return data;
}

指针并不是返回内部数据句柄的唯一途径,引用也会。解决方法一样,对于const对象就应该返回const的引用,对与普通对象才返回普通的引用。

而并不是只有const成员函数需要担心返回句柄的问题,对于非const成员,必须注意,句柄的合法性失效的时间与它所对应的对象完全相同。

30.避免这样的成员函数:其返回值是指向成员的非const指针或引用,但成员的访问级比这个函数低。

这是数据封装所必须的,随意返回被封装的数据的句柄就给了类外部随意修改对象的数据成员的能力,这样是不应该的。

但这种 错误很常见,因为程序员喜欢用引用来传递 来提高效率。传递指针也是如此。

类中的成员函数指针:

typedef void (A::*AmFun)();//AmFun为一个指向A中无参无返回值的函数指针

这里不能使用 typedef void (* fun)() 来指向类A中这个无参无返回值的函数,因为两者类型是不同的,成员函数指针的类型是要有类名称作为前缀的。

也要避免使用成员函数指针将类内封装的函数传递出去。

31.千万不要返回局部对象的引用,也不要返回函数内部new初始化的指针的引用。

局部对象在离开其作用域后就会被系统销毁,而new初始化的指针 是堆中内存,要么在函数的最后内存被释放了,要么没有释放导致内存泄漏。这是比较简单易懂的道理。

前者容易理解,对于后者,有些人说那我们每次调用函数后都规定必须释放这些内存呗,但这显然是不正确的,如果 operator + 返回的是new出内存的指针,对于只进行一次的操作很容易记住要delete指针,但对于多次操作: a+b+c+d+e+f+f 呢,要如何记录并释放这些内存?

32.尽可能的推迟变量的定义。

尽管c中要求将所有的声明都放在前面。但c++中不这么做,目的是减少消耗,定义变量就要调用其构造函数和析构函数,如果这个变量最终未用到,就会造成资源的浪费。

尽可能的减少消耗,如使用复制构造函数,而不是先缺省构造,再赋值。

33.明智的使用内联。

函数有压栈出栈的消耗,宏不安全可靠,而内联函数就非常好。内联函数的优点不只如此。为了处理那些没有函数调用的代码,编译器优化程序进行了专门的设计,也会对内联函数进行一定的优化。

内联函数的代价。增加整个目标代码的体积,程序体积大,计算机内存有限的话,即使有虚拟内存,但程序运行时还是会浪费许多时间在页面调度中,即引发抖动,过多的内联还会降低指令高速缓存的命中率。

内联 inline指令是对编译器的提示,如register一样,事实上大多数编译器会拒绝内联复杂的函数,如包含循环和递归的函数。而且即使最简单的虚函数,编译器也无法内联,其还是会将其放在内存中,并使用虚表中指针指向虚函数。

内联函数一般都放在头文件中,被外联的内联函数会造成的一种错误:如果两个cpp共享同一个头文件,这个头文件中有 inline fun(),如果内联正常,则一切正常,内联代码直接插入在调用的地方,如果内联失败,则在对应cpp中要加载并定义fun函数,而导致两个cpp中包含两个同样的fun函数,当两个目标文件链接时,编译器会为程序中有两个fun函数而报错。旧标准中的解决方法是对于未内联的内联函数,编译器会把它当作声明为static的函数处理,但这样在每个文件中都产生开销。

当程序中要获得一个内联函数的地址时,编译器还要为此生成一个函数体,在旧的规则中每个取内联函数地址的被编译单元会各自生成内联函数的静态拷贝,而新规则下,只会生成一个唯一的内联函数的外部拷贝。

编译器有时会生成构造函数和析构函数的外部拷贝,通过获得这些指针来方便的构造和析构类的对象数组。对于在类内定义的函数都是内联函数。但是对于一个类的构造函数,其并不是只有你所编写的构造函数中的内容,它在编译时会被加入一些代码,如检测是在堆中还是栈中创建对象,对类中成员进行初始化,调用基类的构造函数对基类进行初始化等。这样会使构造函数实际的内容比你想象中还要多,导致无法内联,析构函数也一样。

程序员必须预先估计声明内联函数带来的方面影响。如一个程序库中声明一个内联函数,而对这个内联函数进行升级时,要将所有使用该程序库的用户程序重新编译,而如果这个函数不是内联函数,只要重新链接即可。而如果这个函数的程序库是动态链接的,程序库的修改对用户来说完全是透明的。

内联函数中的静态对象常常出现违违反直觉的行为,如果函数中包含静态对象,要避免将它声明为内联函数。

大多数调试器遇上内联函数无能为力,无法在一个不存在的函数里设置断点。

慎重的使用内联。