C++改善程序与设计条例总结(四)

条款23: 宁可以非成员(no-member)函数、非友元(no-friend)函数替换成员函数。这样组偶可以增加封装性、包裹弹性和技能扩充性。

              (1)推崇封装使得编码者被能自由地改变对象书,改变事物之影响有限客户。面向对象守则要求数据应该尽可能被封装,而成员函数、友元函数带来的封装性比非成员函数、非友元函数要低。由条款22知,成员变量是private,类的成员函数和友元函数可以访问,但随着类的private成分的增加,要访问private成分的函数就会增加,导致类的封装性降低,但是非成员和友元函数无法访问private变量,因此其封装性不会改变;

               (2)非成员函数可以作为其他类的成员,常见的是成为非工具类的非成员,更自由的做法是使用命令空间(namespace),命名空间可以跨越多个源码文件(而类不行),将不同功能的函数防止不同头文件但却在同一个命名空间是比较可取的做法,C++的标准库就是使用此写法,这允许客户对他们所用的一小部分系统形成比编译依赖,并且可以增加头文件轻松拓进行函数拓展,引用函数时只需要包含对应的头文件即可,这对于类来说是不能实现的,即增加机能拓展性。

条款24:如果某个函数的所有参数(包括被this指针所指的那个隐喻参数)都进行类型转换,请使用非成员(no-member)函数。

               例如:oneHalf是类对象,而且支持运算符*的重载的成员函数,对于  

                              result = oneHalf*2;   //没问题,在 non-explicit构造参数情况下 ,相当于result=oneHalf.operator(2);

                              result = 2*oneHalf      //错误,即使是在non-explicit构造参数情况下,相当于result=2.operator*(oneHalf);

             原因是:只有当参数被列为参数列内,这个参数才是隐式类型转换的合格参与者,而被调用之成员函数所隶属的那个对象,即this对象,绝对不是合格的隐式转换参数者,也就是参数2可以进行隐式转换,而第二种写法,2这个常量并不是类对象,也没有重载运算符*,并不能隐式转换为this。当使用非成员函数,不含this,隐式类型转换能确保正确性。

条款25:考虑编写一个不抛出异常的swap函数。swap函数是STL的一部分,对异常安全性编程和自我赋值可能性有着重要意义,其STL中的缺省实现是交换两个对象的值。如果缺省实现版本的效率不足(意味着class或template使用了某种pimpl手法,注释:pimpl是 pointer to implementation的缩写,以指针指向一个对象,内含真正数据。则swap实现即置换内部指针),尝试以下步骤:

               (1)提供一个public swap成员函数,让它高效地置换你的类型的两个对象值,并且这个函数绝对不能抛出异常(因为swap函数的最好的应用就是帮助class提供强烈的异常安全性保障,这一约束只实行于成员函数,不可施加于非成员函数,因为高效的自定义swap函数几乎总是基于内置类型的操作,而内置类型 的操作不会抛出异常);

               (2)在你的class或template所在的命名空间内提供一个非成员版本的swap,并令它调用上述swap成员函数;

               (3)如果你编写的一个class,而非class template,请为你的class特化std::swap(由于std命名空间不允许改变其任何东西,制造特化版本,才能使得该函数专属于自定义的class,并且注意特化std template时,请不要尝试添加任何对于std而言全新的东西,比如定义新的template),并令它调用swap成员函数;

               (4)如果你调用swap,请确保包含一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加任何命名空间修饰符,赤裸裸地调用swap。这样做确保首先调用你自定义的swap版本,在该版本不存在的情况下调用std内的一般版本。

        如:   template<typename T>  void doSomething(T& obj1,T& obj2)

                   {

                                using std::swap;              //令std::swap在此函数内可用

                                ......

                                swap(obj1,obj2);       //为T型对象调用最佳swap版本

                                ......

                   }

条款26:在class定义以及function实现中,尽可能延后变量定义式的出现时间,增加程序的清晰度并改善程序效率。

              (1)所谓的延后变量的定义,不只是延后到直到使用该变量的前一刻,甚至应该延后这份定义直到能够给他初值实参为止,这样不仅能避免变量定义时的构造和析构成本,还可以避免无意义的defautl构造行为,因为通过“default构造函数构造出对象然后再对它赋值”的效率比“直接在构造时制定初值”的效率要低。比如:std::string encrypted;encrypted = password;修改为 std::string encrypted(password);

              (2)对于变量在循环外定义和循环内定义哪一种效率更高,如果class的赋值成本低于一组构造+析构成本,变量循环外定义大体而言高效,但是当循环次数值变得很大的时候,循环内定义定义或许更好,而且循环外定义的变量会加大程序的可理解性和易维护性。

条款27:尽量少做转型动作。如果有个设计需要转型动作,试着发展无需转型的替代设计。如果转型是必要的,试着将它隐藏于某个函数背后,客户随后可以调用该函数,而不需要将转型放进他们自己的代码内。

               (1)C++提供的新式显示转型函数:const_cast 用来移除对象的常量性,dynamic_cast用于决定某个继承体系中的对象安全向下转型,reinterpret_cast执行低级转型,实际动作取决于编译器,static_cast用于强迫隐士转换,如non-const转const、void*转typed指针。新式转型容易在代码中辨别,转型动作的目标窄化,便于编译器诊断错误,更便于使用。

               (2)dynamic_cast实现版本的执行速度很慢,尤其在深度继承或者多重继承中成本更高。通常使用此函数是一个基类指针或者引用转换为子类的对象,避免转换的方式有两种:一种是使用容器直接存储指向子类对象的指针(通常是智能指针);另一种是基类中提供virtual函数的缺省实现。两种方式并非全能,具体情况还得具体分析。

条款28:避免返回handles(包括引用、指针、迭代器)指向对象内部。遵循这一条款可增加封装性,帮助const成员函数的行为像个const,并且发生空悬的handle的可能性降至最低。

             即便是对于返回handles的函数使用返回值和函数都添加const限定,确保返回的handles无权修改对象内部成员,返回的handles所指向的东西也可能不复存在。唯一的关键是,handle作为返回值传出去,就暴露了handle比其所指对象更加长寿的风险,导致空悬的现象。

猜你喜欢

转载自blog.csdn.net/liang285290752/article/details/88363657