Effective C++ 摘记(二)

来源:互联网 发布:换手率软件怎么样 编辑:程序博客网 时间:2024/04/30 06:13


(四)、设计与声明

十八、让接口易用而不误用

类型一致性;


或者可以将 Day, Month, Year 定义为 类,来对输入的参数进行限制。

预防客户错误的另一个方法是,限制类型内什么事情可做,什么事情不可做。常见的限制是加上const, 例如 “以const修饰operator*的返回类型”可以防止客户因用户自定义类型而出错。 if (a * b = c) //愿意是 a*b == c, 如果使用const 修饰 运算符* 的返回类型,则会出现编译错误,从而阻止出错。

为了防止出现工厂模式返回的指针,由于客户疏忽没有被释放,使用 构造指资源的函数直接返回智能指针 shared_ptr 的方式。如:

shared_ptr防范跨DLL错误。

十九、设计class犹如设计type

11条准则。

(1)考虑新type的对象如何被构造和销毁

(2)考虑对象的初始化和对象的赋值有什么区别

(3)考虑新type的对象在使用过程中被 pass by value,其拷贝构造函数需要注意什么地方

(4)考虑新type的对象的私有成员的合法值,在构造函数中可以对其进行约束

(5)考虑新type的继承性能,是否需要将析构函数设置为virtual

(6)考虑新的type需要什么类型的转换,以便在类中实现类型转换函数

(7)考虑什么样的函数和操作符对新type而言是合理的

(8)考虑什么样的标准函数应该驳回,将这些函数设置为 private

(9)考虑什么是新type的“未声明接口”。它对效率、异常安全性以及资源运用提供何种保证。

(10)考虑新type是否足够一般化,若是,考虑定义为一个 class template,而不是一个class。

(11)考虑是否需要定义一个新的类型,或者只需要继承一个现成的类就可以了。


二十、常引用参数代替值传递

如果函数参数为 类的对象,则使用 pass-by-const-reference 比 pass-by-value更加有效。

void  test(const SimpleCalculator& c); //将对象引用声明为const可以避免在函数操作中对对象进行修改。

使用 const 对象引用作为参数,可以避免函数形参为父类,而实际传递实参为子类时造成的 参数切割。使得真正传递进函数的参数只是 父类部分。

如果函数参数为 内置数据类型(如 int, double等)或者 STL的迭代器或者 函数对象,则使用pass-by-value 更加有效。


二十一、需要返回对象时候不要返回引用

函数返回不要返回 stack 栈的局部变量或者heap堆的 全局或者静态变量都不要作为引用或者指针返回。

能返回对象,直接返回对象即可。

二十二、成员变量声明为private

两种访问权限:privateothers

protected并不比public封装性好。

二十三、用非成员函数和非友元函数替换成员函数

封装强度和改变强度成反比,因为只影响有限的用户;

类外访问函数封装性好于累内成员函数的封装性,不增加累内私有数据的访问函数的数量;

使用命名空间,使用 non-member函数来提高封装性。


宁可使用 non-member non-friend的函数来代替 member函数,这样可以增加封装性、包裹弹性和机能扩充性。



二十四、参数需要类型转换应使用非成员函数

针对二元运算符重载。

只有当参数位于参数列内,这个参数才是隐式转换的合格参与者。

class Rational {public:Rational(int numerator = 0 ,int denominator = 1);int numerator() const;int denominator() const;const Rational operator* (const Rational& rhs) const;private:}

Rational   r;

Rational result = r* 2; //因为构造函数没有被声明为 explicit,故会进行隐式转换,将2转换为 Rational对象。

Ration result = 2 * r; //不会自动进行隐式转换,因为2 并不处于Rational 对象参数列表的位置。

为了支持交换运算,可以



如果需要对某个函数的所有参数(包括this 指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个 non-member.


二十五、没有异常的swap函数

类外构造特化的swap函数;

不要在swap的时候产生异常。


如果 swap 的缺省实现效率不足(这几乎总是意味着你的 class(类)或 template(模板)使用了 pimpl idiom 的某种变种),就按照以下步骤来做:

  1. 提供一个能高效地交换你的类型的两个 objects 的值的 public(公有)的 swap member function(成员函数)。出于我过一会儿就要解释的动机,这个函数应该永远不会抛出 exception(异常)。
  2. 在你的 class(类)或 template(模板)所在的同一个 namespace(名字空间)中提供一个 non-member(非成员)的 swap。用它调用你的 swap member function(成员函数)。

  3. 如果你写了一个 class(类)(不是 class template(类模板)),就为你的 class(类)特化 std::swap。让它也调用你的 swap member function(成员函数)
  4. namespace std {  template<>                       // 全特化  void swap<Widget>(Widget& a,     // std::swap                    Widget& b)  {    a.swap(b);                     // <span style="color: rgb(255, 0, 0); font-family: Arial; line-height: 26px; ">不是 class template(类模板)),就为你的 class(类)特化 </span><span style="color: rgb(255, 0, 0); line-height: 26px; font-family: 'Courier New'; ">std::swap</span>  }                                // swap member function}

最后,如果你调用 swap,请确保包含一个 using declaration 使 std::swap 在你的函数中可见,然后在调用 swap 时不使用任何 namespace(名字空间)限定。


(五)、实现

二十六、延后变量定义式

不要提前定义,直到使用改变量的前一刻为之;

针对循环内的对象需要根据构造析构与赋值的成本,以及可维护性进行权衡。




二十七、少做转型操作

Base(*this).virFun()只会影响对象的基类部分的数据副本,不会影响对象本身,如果使用指针类型转换则会无穷递归,去掉虚属性则消除类似问题;

用虚函数的特性代替dynamic_cast

尽量使用C++风格的转型。



二十八、避免返回对象内部数据的引用或指针

破坏了封装型;

函数返回对象析构导致空指针。

返回对象内部数据的引用或者指针,有可能会在外部对对象内部的数据进行修改,或者不经意删除对象内部的数据指针,造成内存错误。

如果函数返回 const的对象内部数据的引用,则可以一定程度上避免外部可能造成的对数据的修改。


函数返回对象析构导致空指针。例如:



二十九、异常安全的努力

对象管理资源;

copy-swap实现技术;

异常安全性取决于最弱安全保证的代码。



三十、inline里里外外

隐式:类内直接定义成(友)员函数,


显式:inline关键字;

由于inlining在大多数c++程序中是编译期间的行为,因此inline函数一般置于 头文件中。

template 通常也置于 头文件中,因为它一旦被使用,编译器为了将它具现化,需要知道长什么样子。

如果正在写一个template,且认为该template具体出来的所有函数都需要inlined,就需要将该template设置为inlined。

inline只是一个对编译器的申请,编译器可以拒绝。大多数编译器会拒绝执行太过复杂的inline,如递归等。同时如果函数中含有 virtual,则也会拒绝inline,因为virtual函数的调用是个动态的行为,只有在执行的时候才能够确定。

拒绝:复杂、虚函数、函数指针调用、模板、构造析构函数、影响动态连接或升级、对调试器的挑战(禁用)。

inline函数实际上没有函数地址,因为它被展开到调用处。如果inline函数发生改动,则使用该函数的程序都必须重新编译。


将大多数inlining限制在小型、被频繁调用的函数身上,这可使得日后的调试和二进制升级更容易,也可使得潜在的代码膨胀问题最小化,使得程序速度提升机会最大化。

不要只因为 function template出现在头文件中,就将他们声明为inline。


三十一、降低文件间编译依存关系

能使用引用和指针完成的不使用对象、用class声明代替定义,并提供不同的头文件——程序库文件和类定义头文件;

将接口Person类和实现类 PersonImpl 分离。在这样的设计下,Person的客户就完全与Dates, Addresses以及Person的实现细节分离了,那些classes的实现修改都不需要Person客户的重新编译,此外客户无法看到Person的实现细节,也不可能写出那些“取决于实现细节”的代码,这就是真正的“接口与实现分离”。

编译依存性最小化的本质: 用声明依存性替代实现依存性。让头文件尽可能自我满足。设计策略为:


handle classinterface class解除了接口与实现的耦合关系。

参考: http://blog.csdn.net/skc361/article/details/27977395


0 0
原创粉丝点击