Effective C++ 改善程序与设计的55个做法,总结笔记(上)

前言

最近在看《Effective C++》这本书,这博客相当于是个浓缩版的总结吧。
在这里你可以大致游览下在 C++ 开发中前人给了我们哪些建议,有机会我觉得最好还是可以看看原书,因为里面会有不少具体的例子告诉你为什么这么做以及这么做的好处。

一、让自己习惯 C++

1. 视 C++ 为一个语言联邦

我们可以视 C++ 为一个由相关语言组成的联邦而非单一语言,例如:

  • C:包括区块 blocks,语句 statements,预处理 preprocessor,内置数据类型 build-in data types,数组 arrays,指针 pointers 等。
  • C++:包括类 classes,封装 encapsulation,继承 inheritance,多态 polymorphism,virtual 函数等。
  • Template C++:泛型编程 generic programming。
  • STL:标准模板库 standard template library。

2. 尽量使用 constenuminline 替换 #define

  • 对于常量,最好使用 const 或者 enum。
  • 对于函数宏,最好使用 inline 函数。

3. 尽可能使用 const

  • 将某些东西声明为 const 可帮助编译器侦测出一些错误用法。
  • 注意 const 和 non-const 是可以发生 重载 的,如果你要这么做,那么令 non-const 版本调用 const 版本可避免代码重复,如:
class TextBlock {
public:
    const char& operator[] (std::size_t position) const {
        // do something
        return text[position];
    }
    char& operator[] (std::size_t position) {
        // 调用 const 版本的重载函数,避免代码重复
        return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
    }
private:
    std::string text;
};

4. 确定对象在使用前初始化

其中构造函数最好使用使用成员初值列,而不是在构造函数中使用赋值操作。如:

Person::Person(const std::string& name, const int age)
    :mName(name), mAge(age) // 成员初始列
{
    // mName = name; // 赋值操作
    // mAge = age; // 赋值操作
}

二、构造、析构、赋值运算

5. 了解 C++ 默默编写和调用的函数

编译器会自动给类创建 default 构造函数copy 构造函数赋值操作符(operator=)析构函数

class Empty {}; // 等同于下面写法

class Empty {
public:
    Empty() {} // default构造函数	
    Empty(const Empty& rhs) {} // copy构造函数
    ~Empty() {} // 析构函数
    Empty& operator=(const Empty& rhs) {} // copy assignment 操作符
};

6. 若不想编译器自动生成上述函数,就要明确拒绝

将相应的成员函数声明为 private,并不予实现即可。

7. 为多态基类声明 virtual 析构函数

如果一个类带有任何 virtual 函数,它都应该拥有一个 virtual 析构函数。

8. 别让异常逃离析构函数

析构函数不要抛出异常。如果析构函数中调用的函数可能抛出异常,那么也要捕捉并吞掉这个异常或结束程序。

9. 绝不在构造函数或析构函数中调用 virtual 函数

在父类构建的过程,virtual 函数还没有下降到子类中去。

10. 赋值操作符都应返回一个 *this 的引用

operator=operator+=operator-= 等等赋值运算符,都返回一个 reference to *this。如:

Widget& operator=(const Widget& rhs) {
    // do something
    return *this;
}

11. 在 operator= 中处理 “自我赋值”

  • 确保任一函数如果操作多个对象时,其中多个对象是同一对象时,其行为仍然正确。
  • 确保当对象自我赋值时有良好的行为。例如:
Widget& operator=(const Widget& rhs) {
    if (this == &rhs) {
        return *this; // 如果是自我赋值,就不做任何事
    }
    // do something
    return *this;
}

12. 复制对象时勿忘其每一个成分

  • 复制函数应该确保复制 “对象内的所有成员变量” 以及 “调用基类适当的复制函数” 完成完整的复制。
  • 不要尝试在赋值运算符(operator=)中调用复制构造函数,亦或是在复制构造函数中调用赋值运算符。如果你的复制构造函数和赋值运算符代码基本一样,消除重复代码的做法是写一个 private 的 init() 方法供两者调用。

三、资源管理

13. 以对象管理资源

  • 把资源放进对象内,我们便可依赖 析构函数 自动调用的机制确保资源被释放。
  • 使用智能指针(如 auto_ptrshared_ptr)来管理资源类,避免你忘记 delete 资源类。

14. 在资源管理类中小心 copy 行为

  • 复制资源类时必须一并复制它所管理的资源,资源的 copy 行为决定资源类的 copy 行为。
  • 常见的 copy 行为有:禁止复制使用引用计数法进行深拷贝转移资源的拥有权

15. 在资源管理类中提供对原始资源的访问

例如提供一个 get() 方法获取原始资源。

16. 成对使用 newdelete 时要采取相同形式

  • 在 new 表达式中使用 [],就必须在 delete 的时候也使用 []。
  • 在 new 表达式中不使用 [],也一定不要在 delete 的时候使用 []。

17. 以独立的语句将 new 的对象置入智能指针中

避免发生异常时,导致难以察觉的资源泄漏产生。如:

std::tr1::shared_ptr<Widget> pWidget(new Widget);
processWidget(pWidget);
// 不建议下面这样做,如果 processWidget 发生异常可能造成泄漏
// processWidget(std::tr1::shared_ptr<Widget> (new Widget));

四、设计与声明

18. 让接口容易被正确使用,不易被误用

  • 保持 “一致性” 使得接口容易被正确使用。例如 STL 容器的接口就十分一致,都有一个名为 size 的成员函数返回目前容器的大小。
  • “阻止误用” 的方法包括建立新类型、限制类型上的操作、束缚对象值,以及消除客户的资源管理责任。

19. 设计 class 犹如设计 type

  • 新 type 的对象应该如何被创建和销毁?
  • 初始化和赋值该有什么样的差别?
  • 新 type 如果被 passed by value(值传递)意味着什么?即复制构造方法该怎么实现。
  • 新 type 存在哪些继承关系?
  • 新 type 需要什么样的转换?即将其它类型转换为 type 类型的行为。
  • 函数或成员的访问权限?public、protected、private。
  • 新 type 有多么一般化?真的需要一个新 type 吗?

20. 宁以 pass-by-reference-to-const 替换 pass-by-value

  • 尽量使用 “引用传递” 参数而不是 “值传递” 参数。前者更加高效且可避免切割问题。
  • 对于 int 等内置类型,以及 STL 的迭代器和函数对象,使用 “值传递” 更好。

21. 必须返回新对象时,别妄想返回其引用

当一个 “必须返回新对象” 的函数,我们就直接返回一个新对象,而不要返回引用或者指针。

22. 将成员变量声明为 private

实现数据的封装性。且可以更方便的控制数据的访问。

23. 宁以 non-member、non-friend 替换 member 函数

  • 这样做可以增加封装性、包裹弹性和机能扩充性。
  • 非成员函数不会增加 “能访问 class 内私有变量” 的函数数量,具有更大的封装性。

24. 如果所有参数都需要类型转换,请采用 non-member 函数

如果一个有理数的类 Rational,我们常常喜欢直接使用 int 类型的数和 Rational 对象进行混合运算。那么使用 non-member 函数将是更好的选择,它允许每一个参数都进行隐式类型转换。

class Rational {
public:
    Rational(int numerator = 0, int denominator = 1) 
        : mNumerator(numerator), mDenominator(denominator) {}
    int numerator() const { return mNumerator; }
    int denominator() const { return mDenominator; }
private:
    int mNumerator; // 分子
    int mDenominator; // 分母
};

const Rational operator*(const Rational& lhs, const Rational& rhs) {
    return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
 
int main() {
    Rational oneFourth(1, 4);
    Rational result;
    result = oneFourth * 2;
    result = 2 * oneFourth; // 如果不是 non-member 的形式将不支持这种写法
}

25. 考虑写出一个不抛异常的 swap 函数

当系统的 std::swap 对你的类型效率不高时,提供一个 swap 成员函数,并确保这函数不会抛出异常。
如果提供一个 member swap,也该提供一个 non-member swap 来调用前者。例如:

class WidgetImpl {
public:
    // ...
private:
    int a, b, c; // 可能有很多数据
    std::vector<double> v; // 意味着复制时间很长
    // ...
};
 
// 这个 class 使用 pimpl 手法
// pimpl 是 pointer to implementation 的缩写
class Widget {
public:
    Widget(const Widget& rhs) {}
    Widget& operator=(const Widget& rhs) {
        // ...
        *pImpl = *(rhs.pImpl);
        // ...
    }
    // 我们需要交换两个 Widget 对象,但是直接使用 std::swap 将导致很多多余的复制
    // 好的做法是只交换两个 WidgetImpl* 指针对象即可,所以我们考虑写一个 swap 方法
    void swap(Widget& other) {
        using std::swap; // 这个声明至关重要,使得C++编译器能够找到正确的函数调用
        swap(pImpl, other.pImpl); // 我们只需要交换 pImpl 即可
    }
private:
    WidgetImpl* pImpl;
};
 
// 提供一个 non-member swap 来调用 member swap
namespace std {
    template<>
    void swap<Widget>(Widget& a, Widget& b) {
        a.swap(b);
    }
}

五、实现

26. 尽可能延后变量定义式的出现时间

将一些变量定义式放在常规的参数检查后面,避免无用的构造方法带来的耗费。有助于增加程序清晰度和改善程序效率。

27. 尽量少做转型

  • 如果可以,尽量避免转型,特别是注重效率的代码中避免使用效率低的 dynamic_cast
  • 如果转型是必要的,尽量将其隐藏在函数背后。客户调用该函数而不需将转型放在自己代码内。
  • 尽可能使用 C++ 风格的转型,而不要使用旧式的 C 风格转型。

28. 避免返回 handles 指向对象内部成分

  • 引用指针迭代器 统统都是所谓的 handles。如果我们返回了对象的 handles,意味着对象被销毁时这个 handles 将会变得空悬,这是比较危险的。
  • 我们应该尽量避免这么做,但有时候你必须这么做,例如 operator[] 操作允许你获取个别元素的引用。

29. 为 “异常安全” 而努力是值得的

  • 基本承诺:如果异常抛出,程序仍保持有效状态,没有对象和数据结构会因此被破坏。
  • 强烈保证:如果函数成功,就是完全成功,如果函数失败,程序会回复到调用前的状态。
  • 不抛掷保证:承诺绝不抛出异常,作用于内置类型身上的所有操作都提供 nothrow 保证。
  • 异常安全函数即使发生异常也不会泄漏资源或允许数据结构败坏。这样的函数区分三种可能的保证:基本承诺、强烈型、不抛异常型。
  • 强烈保证往往能够以 copy-and-swap 实现出来,但它并非对所有函数都具备现实意义。
    备注: copy-and-swap 是指拷贝并修改对象数据副本,函数调用完后,最后在一个不抛出异常的步骤里将修改后的数据和原件替换。

30. 了解 inline 里里外外

  • 将大多数 inlining 限制在小型、频繁调用的函数身上。
  • 不要只因为模板方法出现在头文件中就将它们声明为 inline,除非你认为模板具现化出来的函数都应该被 inline。

31. 将文件间的编译依存关系降至最低

  • 依赖关系复杂导致的问题就是你修改了某个实现却需要编译很多文件,最好是 接口和实现分离
  • 支持 “编译依存最小化” 的一般构想是:相依于声明式,不相依于定义式。基于此构想的两个手段是 Handle classes 和 Interface classes。
  • 程序库头文件应该以 “完全且仅有声明式” 的形式存在。

Effective C++ 改善程序与设计的55个做法,总结笔记(下):

https://blog.csdn.net/afei__/article/details/83624720

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《更有效的C语言编程与设计的35个有效方法》是一本非常实用的书籍,它总结了35个提高C语言编程和设计能力的有效方法。这本书结合实际编程经验,从不同角度介绍了如何更高效地利用C语言进行软件开发。 该书首先从代码的可读性和可维护性方面提出了一些方法。比如,合理命名变量和函数、遵循一定的代码风格、使用注释等,这些方法可以使代码更易于理解和修改,提高工作效率。 其次,该书讲解了一些关于内存管理和指针的技巧。对于C语言的开发者来说,内存管理是一个非常重要的技能。书中通过介绍如何正确使用动态内存分配函数、如何避免内存泄漏等方面来帮助读者提高内存管理的能力。 此外,该书还提供了一些提高代码质量和性能的方法。如代码复用、性能优化等。对于C语言开发者来说,写出高质量、高效率的代码是非常重要的,这本书可以帮助读者掌握一些技巧和原则。 总的来说,这本书内容丰富,通俗易懂,适合C语言的初学者和有一定基础的开发者阅读。它可以帮助读者全面提高C语言编程和设计的能力,提升工作效率。无论是想从事C语言开发还是提升编程技能的人,都可以从中受益匪浅。 ### 回答2: 《more effective c: 35个改善编程与设计的有效方法(中文版) 》是一本非常实用的书籍,它提供了许多改善编程与设计的有效方法。以下是对该书的回答: 这本书共包含了35个方法,旨在帮助读者提高编程和设计的效率。它首先介绍了良好的编程风格和规范,包括命名规则、代码布局、注释等。这些方法可以使代码更易于阅读和维护,并提高代码的可重用性和可扩展性。 接下来,该书介绍了一些常见的编程错误和陷阱,并提供了相应的解决方案。例如,它说明了内存管理的重要性,并给出了避免内存泄漏和悬挂指针的方法。 此外,该书还介绍了一些高级的编程技术和设计模式,如多线程编程、异常处理和继承等。这些方法可以帮助读者编写更健壮和可靠的程序,并提高程序的性能和响应能力。 另外,该书还强调了测试和调试的重要性,并介绍了一些常用的测试工具和技术。它提供了一些测试和调试的实用方法,帮助读者发现和修复程序中的错误和缺陷。 总的来说,《more effective c: 35个改善编程与设计的有效方法(中文版) 》是一本非常实用的书籍,它提供了许多实用的方法和技巧,帮助读者提高编程和设计的效率。无论是初学者还是有经验的开发者,都可以从中受益,并提升自己的编程能力。 ### 回答3: 《more effective c :35个改善编程与设计的有效方法(中文版) .pdf》是一本关于优化编程和设计的有效方法的书籍。 这本书共包含了35个有效的方法,可以帮助程序员和设计师改进他们的工作。在这本书中,作者提供了一些实用的技巧和经验,帮助读者提高他们的编程和设计技能。 这本书的价值在于它提供了实用的方法和步骤,读者可以按照这些方法和步骤进行实施,从而实现更有效的编程和设计。这本书涵盖了多个方面,包括代码的优化、错误的处理、算法的选择、设计模式的应用等等。 通过阅读这本书,读者可以了解到如何更好地组织和管理代码,以及如何选择合适的算法和数据结构来提高程序的效率。此外,这本书还介绍了一些设计模式和原则,读者可以学习如何使用它们来提高程序的灵活性和可维护性。 总之,这本书提供了一些实用的方法和技巧,帮助读者改进他们的编程和设计技能。对于那些希望在编程和设计领域取得更好成果的人来说,这本书是一个很好的参考资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值