Effective C++学习笔记

总结: 囫囵吞枣地看完了EffectiveC++这本书,收获不少。收获主要是以下几个方面:

  • 编程注意细节
    • 提高程序效率的细节比如const
    • is-a 和 has-a的区别
    • pure-virtual、impure-virtual和non-virtual之间继承该如何处理
    • 继承中隐藏基类的成员等
  • 设计方法
    • 什么时候该继承(is-a),什么时候该以对象作为data member(is-inplemented-in-terms-of);
    • Template method、 Strategy, 抽象工厂等设计模式
    • 泛型编程与模板编程
  • 异常安全性
    • 异常处理
    • copy-swap技术

由于没有太多的实战经历,所以有些东西并不是很懂。 所幸在看书过程中,做过一些笔记整理,其中 大部分内容摘抄自书上,一小部分加上自己的一点点理解,方便日后回顾。

  • 02: 对于单纯常量,最好以const对象或 enums 替换#define

  • 03: 尽可能使用const

    • mutable 关键字表征 const成员函数可以修改mutable成员
  • 04: 为内置对象进行手工初始化,C++并不保证初始化他们;

  • 05: 在类中,如果有引用 或const对象,记得要自己编写构造函数,拷贝构造函数等,因为编译器自己生成的相关函数很大可能达不到要求。

  • 06: 为了驳回编译器自动提供的功能(构造函数,拷贝等),可以将相应的member function declared private,并且不予实现; 或者使用像Uncopyable这样的base class;

  • 07: 当类作为基类的时候,有可能表现出多态的时候,应将虚构函数设计成virtual

  • 09: 绝不在构造函数和析构函数中调用virtual function;
    如果在base class 调用了virtual function,在派生类的构造函数初始化基类的时候,实际上还是调用的是base class的function,并没有调用dervied class的内容(此时derived class 并没有完成初始化)

  • 10: 令 operator= 返回一个referece to *this (方便连续赋值 如 a = b = c;);

  • 11: 在operator=中处理“自我赋值”copy and swap 技术实现自我赋值;

class Widgt {
void swap(Widgt& rhs);// 交换*this 和 rhs的数据

}

Widget& Widget::operator=(const Widget& rhs) 
{
     Widget tmp(rhs);
     swap(rhs);
     return *this;
}
  • 12: 复制对象的时候务必记住每一个成员
    copying函数应该确保复制“对象内的所有成员变量” 以及“所有base Class 的成分”

  • 13: 以对象管理资源 记得用智能指针管理资源;

  • 14: 在资源管理类中心小心copying行为

    • 当资源管理类需要copy的时候 需要从以下几个方面考虑
      • 禁止复制(将拷贝构造函数,以及拷贝赋值函数 设置于private有点类似于unique_ptr<>)
      • 对底层资源使用“引用计数法” 模仿“shared_ptr”
      • 复制底部资源(深拷贝,每一份对象都有自己的heap内存)
      • 转移资源的管理权(有点像 auto_ptr )
  • 15: 资源管理类中需要提供对原始资源的访问

    • 智能指针类可以隐式转化为底部的原始指针,便于访问底部的成员
    • 可以有显式转换(较为安全)和隐式转换(对客户比较安全)
  • 16: 成对使用new 和delete 尤其注意在利用typedef中声明新的类型名时(尽量少用typedef声明数组类型)

  • 17: 以独立语句将new出来的对象置入智能指针

Design and Declarations

  • 18: make interfaces easy to use correctly and hard to use incorrectly
    • 令自己的types和内置types的行为一致
    • 尽量保持接口一致性 如STL容器的接口
  • 19: Treat class design as type design
    • type的对象应该如何该创建和销毁;
    • 对象的初始化和赋值该有什么样的区别
    • 新type的对象如果pass by value 意味着什么
    • 新type的合法值 ?哪些赋值是合法的
    • 是否需要配合某个继承图系(inheritance graph)吗?
    • 什么样的类型转换?
    • 什么样的操作符和函数对此新type而言是合理的?(设计接口)
    • 什么样的标准函数应该被驳回?(设计为private)
    • 谁该取用新type的成员? (设计为public)
  • 20: 以pass-by-reference to const 替换pass-by-value
    • 前者比较高效,同时可以避免切割问题;
    • 对于内置类型、STL的迭代器和函数对象。 对他们而言,pass-by-value往往比较合适
  • 21: Don’t try to return a reference when you must return an object;
    • 绝对不要返回pointer或reference指向一个local stack对象(会产生未定义的行为)
    • 绝对不要返回指向一个heap-allocated对象(无法控制对象的销毁)
    • 绝对不要返回pointer或reference指向一个local static对象(可能需要多个这样的对象)(在多线程情况下也难以保证安全)
  • 22: Declare data members private
    • 可以实现对data member的各种权限控制
    • 有利于封装,注意protected 并不比public更具有封装性
  • 23: Prefer non-member non-friend替换member函数
    • 其实是具有更大的封装性(注意namespace)
    • 增加封装性,packaging flexibility 和机能扩充性
  • 24: 若所有参数需要类型转换,请将此函数设计成non-member函数
    • 有理数类(Rational)的的四则运算; Rational tmp = Rational() * 2 ; Rational tmp = 2 * Rational(); 考虑这两种情况
  • 25 : Consider support for a non-throwing swap( 不抛异常)

    • 缺省版本swap
    • 如果缺省版本的效率不够,可以尝试做

      • 提供一个public swap函数(这个函数,不要抛出异常)
      • 在class 或template所在的命名空间内提供一个non-member swap,并调用上述的swap成员函数
      • 如果正编写一个class(非template),需要特化std::swap, 并令他调用你的成员swap
    • 注意: 不要忘std里加入任何新的东西

// default swap
template <typename T>
void swap(T& a, T& b)
{
     T temp(a);
     a = b;
     b = tmp;
}

class Widget {
 publicvoid swap(Widget &other);
}
  • 26: 尽可能延后变量定义式的出现的时间(用到的时候才再去声明)
    • 循环里用到的变量尽量在循环里声明,除非
      • 赋值成本比构造加析构低,
      • 处理代码中效率高度敏感的部分
  • 27: 尽量少做转型动作(如果转型必要,就封装在函数里面)(新式转型 好于 旧式转型)

    • const_cast
    • dynamic_cast
    • static_cast
    • reinterpret_cast 不适合移植
  • 28: Avoid returning “handles”to object internals

    • handles (pointer, reference, iterators)
  • 29: Strive for exception safety code(为异常安全性努力是值得的)

    • 带有异常安全性的函数会:
      • 不泄露任何资源
      • 不允许数据败坏
    • 异常安全函数(Exception-safe code)提供下面三个保证之一

      • 基本承诺

        • 程序内的任何事物都保持在有效状态下(对象或者数据结构不会有因此而损坏)
        • 当异常时,可以选用默认状态,也可以回复到变换之前的状态
      • 强烈保证(往往以copy-and-swap实现出来)

        • 函数成功,就完全成功,如果失败,程序会回复到调用函数之前的状态
      • nothrow 保证
  • 30: Understand the ins and outs of inlining

    • virtual 和inline同时出现,编译器往往会拒绝inline
    • inline限制在小型、频繁调用的函数身上
    • 构造函数最好不要inline
  • 31: 将文件间的编译依存关系降至最低

    • 接口与实现的分离
      • 如果使用object reference 或者 object pointers可以完成任务,就不要使用objects
      • 如果能够,尽量以class声明式替换定义式
      • 为声明式和定义式提供不同的头文件
  • 32: 确定public继承是is-a的关系(注意区分has-a)

    • 适用于derived class身上的所有事情都能适用于base class身上
  • 33: Avoid hiding inherited names(避免隐藏继承的成员名)

    • 避免隐藏基类的成员名 可以使用using 声明式或者 forwarding function
  • 34: Differentiate between inheritance of interface and inheritance of implementation(区分接口继承和实现继承)

    • pure virtual: 为了让derived class继承函数接口(继承了的具象类都要重新声明)
    • impure virtual: 继承该函数的接口和缺省实现
    • non-virtual:为了令derived classes继承函数的接口及一份强制性实现
  • 35: 考虑virtual函数以外的其他设计方式(提及设计模式中的Template Method 和 Strategy)

    • 令客户通过public non-virtual成员函数间接调用private函数(NVI手法)
/// Template Method
class GameCharacter {
public:
    int healthValue() const
    {
        ···  // 做一些事前工作
        int retVal = doHealthValue();
        ··· //做一些事后工作
        return value;
    }
    ···
private:
    virtual int doHealthValue() const
    {
        ··· // 缺省算法,计算健康指数
    }   
}
    • 借由function pointers实现strategy模式
class GameCharacter;
int defaultHealthCalc(const GameCharacter& gc);

class GameCharacter {
public:
    typedef std::function<int (const GameCharacter&)> HealthCalcFunc;
    explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc)
        : healthFunc(hcf) {}

    int healthValue() const
    {    return healthFunc(*this);}

private:
    HealthCalcFunc healthFunc;
};

/// 可以转换成function对象
short calcHealth(const GameCharacter&); // 返回类型 non-int

struct HealthCalculator {    //为计算健康值设计的函数对象
    int operator()(const GameCharacter&) const
    {···}
};

class GameLevel {
public:
    float health(const GameCharacter&) const; // 成员函数
    ···
};
    • 使用non-virtual interface手法,那是Template Method设计模式的一种特殊形式,它以public non-virtual成员函数包裹性较低访问性(private或protected)的virtual函数
    • 将virtual函数替换为“函数指针成员变量”,这是strategy设计模式的一种分解表现形式
    • 以function成员变量替换virtual函数, 因此允许使用任何可以转变成(callable entity)function对象的表达式,也是strategy的一种表现形式
  • 36: Never redefine an inherited non-virtual function.

    • 如果重新定义从基类继承的non-virtual function, 那么就应该是设计出现了问题
  • 37: 绝不重新定义继承而来的缺省参数值(针对virtual函数)

    • virtual函数系动态绑定,而缺省参数值却是静态绑定
    • 如果需要缺省值,应该将该函数设计为non-virtual函数,再设计一个private的virtual function 如同前面介绍的Template Method设计
  • 38: Model “has-a”or “is-implementated-in-terms-of” through composition

    • 在应用域内,是has-a的关系
    • 在实现域中,是根据某物实现出 例如STL库中的stack是以vector为底层数据结构实现的
  • 39: 明智而谨慎地使用private继承

    • private继承某种意义上说 is-implemented-in-terms-of, 所以尽可能使用38条中使用复合的方式实现
    • 除非确定private继承可以造成empty base最优化
  • 40: 明智而谨慎地使用多重继承

    • 多重继承比单一继承复杂;以及可能会对virtual继承的需要
    • virtual继承会增加大小,速度,初始化复杂度等成本

模板与泛型编程

  • 41: 了解隐式接口和编译器多态

    • 以不同的template参数具现化function templates会导致调用不同的函数,这就是编译期多态(有点像重载函数)
    • classes 和 templates都支持接口(interface)和多态(polymorphism)
    • 对classes而言接口是显式的,以函数签名为中心。多态则是通过virtual函数发生于运行期
    • 对template参数而言,接口是隐式(implicit)的,奠基于有效表达式;多态则是通过template具现化发生于编译期
  • 42: 了解typename的双重意义

    • 声明template参数时,class和typename可以互换
    • 记得使用typename标识嵌套从属类型名称;但不要在base class List或 member initialization list内以它作为base class的修饰符
  • 43: 学习处理模板化基类内的名称

    • 继承模板基类的时候,基类的成员函数不可见;这个时候可以用一下三种方式解决
      • 在前面添加this指针
      • 利用using语句,使模板基类的函数可见
      • 利用基类的类名加::调用基类函数
  • 44: 将与参数无关的代码抽离template

    • template生成多个classes和多个function,所以任何template代码都不该与某个造成膨胀(怎么造成代码膨胀呢?)的template参数产生相依关系
    • 因非类型模板参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数
    • 因类型参数(type parameters)而造成的代码膨胀,往往可以降低,做法是让带有王权相同的二进制表述的instantiation types共享实现码
  • 45: 运用成员函数模板接受所有的兼容类型

    • 请使用member function templates生成“可接受所有兼容类型”的函数
    • 如果你声明member templates 用于“泛化copy构造”或泛化assignment操作,还是需要声明正常的拷贝构造函数和copy assignment操作符
  • 46: 需要类型转换时请为模板定义非成员函数
    • 编写class template,需要支持与此template相关函数 支持参数之隐式类型转换 时,请将那些函数定义为 class template内部的friend函数
  • 47: 请使用traits classes表现类型信息

    • Trait classes使得“类型相关信息”在编译器可以用,以template特化实现
    • 整合overloading技术
  • 48: 认识template元编程

  • 49: 了解new-handler的行为

    • new-handler函数要完成以下的事情:

      • 让更多内存可被使用
      • 安装另一个new-handler(令new-handler修改会影响new-handler行为的static数据,global数据)
      • 卸除new-handler(将null指针传递给set_new_handler)
      • 跑出bad_alloc(或派生出来的)异常
    • Nothrow new是一个颇为局限的工具,因为它只适用于内存分配 够来的构造函数还是可能跑出异常

  • 50: 了解new和delete的合理替换时机

  • 51: 编写new和delete时需要固守常规

  • 52: 写了placement new也要写placement delete(不熟悉)

  • 53: pay attention to compiler warnings

    • 编译器的警告能力是不一样的; 编写程序,争取完全无警告
  • 54: 让自己熟悉TR1在内的标准程序库(现在C++11基本都实现了)

  • 55: 让自己熟悉boost程序库(泛型编程, 模板元编程)

没有理解和不太懂的地方,也有几处,主要集中于:

  • 泛型编程
  • 模板元编程
  • trait type、trait class, 特化以及偏特化

接下来的学习之路也需要偏重以下上述方面。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值