【C++】Effective C++学习

Effective C++——改变程序与设计的55个具体做法。

文章目录


Effective C++在线地址: https://wizardforcel.gitbooks.io/effective-cpp/content/index.html

1. 将C++视为federation of languages(语言联合体)

最初的C++只是在C基础上增加了class(面向对象)的特性,而现在它已经成为融合多个子语言特性的联合体,即包括C、Object-Oriented C++、Template C++、STL

2. 用consts, enums 和 inlines取代#defines

前者是属于代码本身的,而后者是属于预处理部分;因此这一部分也可以称之为:用编译器compiler取代预处理器preprocessor

3. 只要可能就用const

将某些东西声明为 const 有助于编译器发现使用错误。const 能被用于任何 scope(范围)中的 object(对象),用于 function parameters(函数参数)和 return types(返回类型),用于整个 member functions(成员函数)。

4. 确保objects对象在使用前被初始化

一个更好的方式是在构造函数中使用初始化列表,而不是一个个赋值。

在初始化时,要考虑是声明在全局还是局部。

5. 了解 C++ 为你偷偷地加上和调用了什么函数

编译器可以隐式生成一个 class(类)的 default constructor(缺省构造函数),copy constructor(拷贝构造函数),copy assignment operator(拷贝赋值运算符)和 destructor(析构函数)。

6. 如果不想使用compiler-generated functions编译器生成函数,就明确拒绝

为了拒绝编译器生成函数,将相应的 member functions(成员函数)声明为 private,而且不要给出 implementations(实现)。使用一个类似 Uncopyable 的 base class(基类)是方法之一。

7. 在 polymorphic base classes(多态基类)中将 destructors(析构函数)声明为 virtual(虚拟)

polymorphic base classes(多态基类)应该声明 virtual destructor(虚拟析构函数)。如果一个 class(类)有任何 virtual functions(虚拟函数),它就应该有一个 virtual destructor(虚拟析构函数)。

不是设计用来作为 base classes(基类)或不是设计用于 polymorphically(多态)的 classes(类)就不应该声明 virtual destructor(虚拟析构函数)。

8. 防止因为 exceptions(异常)而离开 destructors(析构函数)

destructor(析构函数)应该永不引发 exceptions(异常)。如果 destructor(析构函数)调用了可能抛出异常的函数,destructor(析构函数)应该捕捉所有异常,然后抑制它们或者终止程序。

如果 class(类)客户需要能对一个操作抛出的 exceptions(异常)做出回应,则那个 class(类)应该提供一个常规的函数(也就是说,non-destructor(非析构函数))来完成这个操作。

9. 绝不要在 construction(构造)或 destruction(析构)期间调用 virtual functions(虚函数)

在 construction(构造)或 destruction(析构)期间不要调用 virtual functions(虚拟函数),因为这样的调用不会转到比当前执行的 constructor(构造函数)或 destructor(析构函数)所属的 class(类)更深层的 derived class(派生类)。

10. 让 assignment operators(赋值运算符)返回一个 reference to *this(引向 *this 的引用)

遵守如下惯例:

class Widget {
    
    
public:
  ...
Widget& operator=(const Widget& rhs)   // return type is a reference to
{
    
                                          // the current class
  ...
  return *this;                        // return the left-hand object
  }
  ...
};

11. 在 operator= 中处理 assignment to self(自赋值)

当一个 object(对象)被赋值给自己的时候,确保 operator= 是行为良好的。技巧包括比较 source(源)和 target objects(目标对象)的地址,关注语句顺序,和 copy-and-swap。

如果两个或更多 objects(对象)相同,确保任何操作多于一个 object(对象)的函数行为正确。

12. 拷贝一个对象的所有组成部分

拷贝函数应该保证拷贝一个对象的所有数据成员以及所有的基类部分。

不要试图依据一个拷贝函数实现另一个。作为代替,将通用功能放入第三个供双方调用的函数。

13. 使用对象管理资源

为了防止资源泄漏,使用 RAII 对象管理资源,在 RAII 对象的构造函数中获得资源并在析构函数中释放它们。

两个通用的 RAII 是 tr1::shared_ptr 和 auto_ptr。tr1::shared_ptr 通常是更好的选择,因为它的拷贝时的行为是符合直觉的。拷贝一个 auto_ptr 是将它置为空。

14. 谨慎考虑资源管理类的拷贝行为

为了确保你从不会忘记解锁一个被你加了锁的 Mutex,你希望创建一个类来管理锁。RAII 原则规定了这样一个类的基本结构,通过构造函数获取资源并通过析构函数释放它:

class Lock {
    
    
public:
  explicit Lock(Mutex *pm)
  : mutexPtr(pm)
  {
    
     lock(mutexPtr); }                          // acquire resource

  ~Lock() {
    
     unlock(mutexPtr); }                // release resource

private:
  Mutex *mutexPtr;
};

拷贝一个 RAII 对象必须拷贝它所管理的资源,所以资源的拷贝行为决定了 RAII 对象的拷贝行为。

15. 在资源管理类中准备访问裸资源(raw resources)

API 经常需要访问裸资源,所以每一个 RAII 类都应该提供取得它所管理的资源的方法。

访问可以通过显式转换或者隐式转换进行。通常,显式转换更安全,而隐式转换对客户来说更方便。

16. 使用相同形式的 new 和 delete

如果在 new 表达式中使用了 [],就必须在对应的 delete 表达式中使用 []。

17. 在一个独立的语句中将 new 出来的对象存入智能指针

用一个单独的语句创建 Widget 并将它存入一个智能指针,然后将这个智能指针传递给 processWidget:

std::tr1::shared_ptr<Widget> pw(new Widget);  // store newed object
                                              // in a smart pointer in a
                                              // standalone statement

processWidget(pw, priority());                // this call won't leak

18. 使接口易于正确使用,而难以错误使用

好的接口易于正确使用,而难以错误使用。你应该在你的所有接口中为这个特性努力。

使易于正确使用的方法包括在接口和行为兼容性上与内建类型保持一致。

预防错误的方法包括创建新的类型,限定类型的操作,约束对象的值,以及消除客户的资源管理职责。

tr1::shared_ptr 支持自定义 deleter。这可以防止 cross-DLL 问题,能用于自动解锁互斥体。

19. 视类设计为类型设计

20. 用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)

用传引用给 const 取代传值。典型情况下它更高效而且可以避免切断问题。

这条规则并不适用于内建类型及 STL 中的迭代器和函数对象类型。对于它们,传值通常更合适。

21. 当你必须返回一个对象时不要试图返回一个引用

绝不要返回一个局部栈对象的指针或引用,绝不要返回一个被分配的堆对象的引用,如果存在需要一个以上这样的对象的可能性时,绝不要返回一个局部 static 对象的指针或引用。

22. 将数据成员声明为 private

声明数据成员为 private。它为客户提供了访问数据的语法层上的一致,提供条分缕析的访问控制,允许不变量被强制,而且为类的作者提供了实现上的弹性。

protected 并不比 public 的封装性强。

23. 用非成员非友元函数取代成员函数

用非成员非友元函数取代成员函数。这样做可以提高封装性,包装弹性,和机能扩充性。

24. 当类型转换应该用于所有参数时,声明为非成员函数

如果你需要在一个函数的所有参数(包括被 this 指针所指向的那个)上使用类型转换,这个函数必须是一个非成员函数。

25. 考虑支持不抛异常的 swap

如果 std::swap 对于你的类型来说是低效的,请提供一个 swap 成员函数。并确保你的 swap 不会抛出异常。

如果你提供一个成员 swap,请同时提供一个调用成员 swap 的非成员 swap。对于类(非模板),还要特化 std::swap。

调用 swap 时,请为 std::swap 使用一个 using declaration,然后在调用 swap 时不使用任何 namespace 限定条件。

26. 只要有可能就推迟变量定义

只要有可能就推迟变量定义。这样可以增加程序的清晰度并提高程序的性能。

27. 将强制转型减到最少

避免强制转型的随时应用,特别是在性能敏感的代码中应用 dynamic_casts,如果一个设计需要强制转型,设法开发一个没有强制转型的侯选方案。

如果必须要强制转型,设法将它隐藏在一个函数中。客户可以用调用那个函数来代替在他们自己的代码中加入强制转型。

尽量用 C++ 风格的强制转型替换旧风格的强制转型。

28. 避免返回对象内部构件的“句柄”

避免返回对象内部构件的句柄(引用,指针,或迭代器)。这样会提高封装性,帮助 const 成员函数产生 const 效果,并将空悬句柄产生的可能性降到最低。

29. 争取异常安全(exception-safe)的代码

即使当异常被抛出时,异常安全的函数不会泄露资源,也不允许数据结构被恶化。这样的函数提供基本的,强力的,或者不抛出保证。

强力保证经常可以通过 copy-and-swap 被实现,但是强力保证并非对所有函数都可用。

一个函数通常能提供的保证不会强于他所调用的函数中最弱的保证。

30.

……

以上。

猜你喜欢

转载自blog.csdn.net/qq_40344790/article/details/129786348