• 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 * 操作符。假设选择申请一个新的内存来存放数据。并返回这块新数据的指针的话,速度较慢。且内存easy泄漏。而最好的方法应该是使这个指向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初始化的指针 是堆中内存。要么在函数的最后内存被释放了,要么没有释放导致内存泄漏。这是比較简单易懂的道理。

    前者easy理解,对于后者,有些人说那我们每次调用函数后都规定必须释放这些内存呗,但这显然是不对的。假设 operator + 返回的是new出内存的指针。对于仅仅进行一次的操作非常easy记住要delete指针。但对于多次操作: a+b+c+d+e+f+f 呢。要怎样记录并释放这些内存?


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

    虽然c中要求将全部的声明都放在前面。但c++中不这么做,目的是降低消耗。定义变量就要调用其构造函数和析构函数,假设这个变量终于未用到,就会造成资源的浪费。

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


    33.明智的使用内联。

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

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

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

    内联函数一般都放在头文件里,被外联的内联函数会造成的一种错误:假设两个cpp共享同一个头文件。这个头文件里有 inline fun(),假设内联正常。则一切正常,内联代码直接插入在调用的地方,假设内联失败,则在相应cpp中要载入并定义fun函数,而导致两个cpp中包括两个相同的fun函数。当两个目标文件链接时,编译器会为程序中有两个fun函数而报错。

    旧标准中的解决方法是对于未内联的内联函数。编译器会把它当作声明为static的函数处理,但这样在每一个文件里都产生开销。

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

    编译器有时会生成构造函数和析构函数的外部拷贝。通过获得这些指针来方便的构造和析构类的对象数组。

    对于在类内定义的函数都是内联函数。可是对于一个类的构造函数。其并非仅仅有你所编写的构造函数中的内容,它在编译时会被增加一些代码,如检測是在堆中还是栈中创建对象,对类中成员进行初始化,调用基类的构造函数对基类进行初始化等。

    这样会使构造函数实际的内容比你想象中还要多。导致无法内联,析构函数也一样。

    程序猿必须预先预计声明内联函数带来的方面影响。如一个程序库中声明一个内联函数,而对这个内联函数进行升级时。要将全部使用该程序库的用户程序又一次编译,而假设这个函数不是内联函数,仅仅要又一次链接就可以。

    而假设这个函数的程序库是动态链接的,程序库的改动对用户来说全然是透明的。

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

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

    谨慎的使用内联。


  • 相关阅读:
    使用MOCK对象进行单元测试
    软件项目管理的圣经人月神话(中)
    java中使用MD5进行计算摘要
    Windows平台安装Bugzilla(上)
    dom4j学习总结(二)
    深入解析ATL(第二版ATL8.0)(2.12.2节)
    深入了解JUnit 4
    java中关于时间日期操作的常用函数
    使用XStream需注意的问题
    Windows平台安装Bugzilla(下)
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/6734079.html
Copyright © 2020-2023  润新知