effective C++学习笔记

本文档详述了C++编程的最佳实践,涵盖C、面向对象C++、模板C++和STL。强调了const的使用以提高错误检查,推荐使用enum、inline函数和成员初始化列表代替#define,讨论了const和non-const成员函数的重载,以及在构造和析构过程中避免异常和virtual函数调用。此外,讲解了智能指针的使用,资源管理的RAII原则,以及避免资源泄漏的方法。同时,文章探讨了类型设计、接口设计与声明,如pass-by-reference-to-const和隐藏成员变量,提倡使用non-member函数以及template元编程。最后,提到了定制new和delete的场景以及处理内存分配失败的情况。
摘要由CSDN通过智能技术生成

# ======

# 让自己习惯C++

# ======

# 第一条 (1)c++高效编程守则视状况而变化,取决于你使用c++的哪一部分。

1. C

2. Object-Oriented C++

3. Template C++

4. STL

# 第二条 (1)对于单纯常量,最好以const对象或enums替换#define;对于形似函数的宏,最好改用inline函数替换#define。

1. #define 报错时可能不会提到你用到的宏定义,而是宏定义的内容,因此你可能有一些迷惑。

2. 取枚举的地址是不合法的,因此不想让别人获取整形常量的地址或者指针,类内可以用枚举实现,也不会导致非必要的内存分配。

# 第三条 (1)将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体;编译器强制实施bitwise(const成员函数无法修改非静态成员变量,可用mutable强制修改),但你编写程序时应该使用“概念上的常量性”(对对象而言可接受,但是编译器不接受);(2)当const和non-const成员函数有着实质等价的实现时,领non-const版本调用const版本可避免代码重复。

1. const vector<int>::iterator iter:无法改变迭代器,但是可以改变迭代器指向的值。vector<int>::iterator iter:无法改变迭代器指向的值,但是可以改变迭代器。

2. 成员函数的const属性可以进行重载。

3. operator[]的返回值是&,因此可以对其进行赋值修改,如果不是&,则赋值修改的只是其一个副本。

4. mutable声明的非静态成员变量,可以无惧bitwise constness约束,在const成员函数中进行修改。

5. 同样功能的同名函数:const成员函数可以被非const成员函数调用,进而减少代码重复,但是非const成员函数不能被const成员函数调用,因为const成员不对成员变量进行修改,这样违背了函数设计的原则。

# 第四条 (1)为内置型对象进行手工初始化,因为C++不保证初始化它们;(2)构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和他们在class中的声明次序相同;(3)为免除“跨编译单元之初始化次序”问题,晴以local static对象替换non-local static对象。

1. 无参数构造函数

   ABEntry::ABEntry()

   :theName(),theAddress(),thePhones(),numTimesConsulted(0){}

   内置类型一定要初始化,不然很容易开启“不明确行为”的潘多拉盒子。

2. 不同文件内的non-local static变量初始化顺序是不明确的,因此使用未初始化的变量可能会导致问题。解决:将non-local static对象搬到函数内,进行函数的调用,保证首次遇到该对象时进行初始化,这是Singleton模式的一个常见手法。这种函数也比较使用声明为inline函数。

# ======

# 构造/析构/赋值运算

# ======

# 第五条 (1)编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。

1. 类中含有引用的成员变量,必须自己定义拷贝赋值操作符,因为C++不允许让reference指向不同对象,以及不允许该reference被修改,影响其他持有该reference指向的对象。同理const成员操作符也是一样的。

2. base class的拷贝赋值函数声明为private,则编译器无法为derived class生成默认的copy assignment操作符。

# 第六条 (1)为驳回编译器自动提供的功能,可将相应的成员函数声明为private并且不予实现。使用Uncopyable这样的base class也是一种拒绝派生类被copy的做法。

# 第七条 (1)带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数;(2)Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。

1. derived对象由base class指针删除,而base class带着一个non-virtual析构函数,其执行通常是对象的derived成分没有被销毁。一个不用做base class的类不应该被声明为虚析构函数,会导致对象体积增大以及不具备较好的移植性。

2. 纯虚函数是可以定义缺省实现的,需要一个abstract class作为借口,可以定义其纯虚析构函数,并给出缺省实现。

# 第八条  (1)析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序;(2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

# 第九条  (1)在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)

1. 可以通过在derived class 中构造函数的初始化列表中调用父类构造函数(构造子类时候显式调用父类构造函数),传递信息。

2. class Transaction{

3. public:

      explict Transaction(const std::string& logInfo);

      void logTransaction(const std::string& logInfo) const;

      ...

      };

   Transaction::Transaction(const std::string& logInfo)

   {

    ...;

    logTransaction(logInfo);

   }

   class BuyTransaction : public Transaction {

    public:

    BuyTransaction(parameters):Transaction(createLogString(parameters))//初始化列表调用父类构造函数

    {...}

    private:

    static std::string createLogString(parameters);//静态函数

   }

# 第十条 (1)令赋值操作符返回一个reference to *this

1. 为了赋值后的变量成为左值,仍然可以接受再次赋值。(a=b)=c 失败;a=b=c 成功;

# 第十一条 (1)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址(判断地址是否相同)、精心周到的语句顺序(delete后移)、以及copy and swap;确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

# 第十二条 (1)Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”;(2)不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

# ======

# 资源管理

# ======

# 第十三条 (1)为防止资源泄漏,请使用RAII对象,它们再构造函数中获得资源并再析构函数中释放资源;(2)两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向nullptr。

1. auto_ptr shared_ptr两者都在其析构函数中做delete而不是delete[],因此再动态分配的array身上使用这两个智能指针是一个馊主意。

# 第十四条 (1)复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为(自己编写);(2)普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。

1. shared_ptr可以指定删除器,即引用计数为0进行的操作

   class Lock{

    public:

        explict Lock(Mutex* pm):mutexPtr(pm,unlock)

        {

          lock(mutexPtr.get());

        }

      private:

        std::tr1::shared_ptr<Mutex> mutexPtr;

   }

# 第十五条 (1)APIs往往要求访问原始资源,所以每一个RAII class应该提供一个“取得其所管理之资源”的办法;(2)对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便(可能造成意外的事情)。

1. shared_ptr auto_ptr都提供一个get成员函数,用来执行显式转换,返回智能指针内部的原始指针。其也重载了->和*可以访问指针内容。

2. 隐式转换:

   class Font{

    public:

    ...

    operator FontHandle() const

    {return f;}

    private:

    FontHandle f;

   }

# 第十六条 (1)如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要再相应的delete表达式中使用[]。

# 第十七条 (1)以独立语句将newed对象存储于智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。

1. processWidget(std::rt1::shared_ptr<Widget>(new Widget),priority),因为执行顺序的不同,可能导致内存泄漏。1 new Widget  2 priority 3 tr1::shared_ptr构造函数。

# ======

# 设计与声明

# ======

# 第十八条  (1)好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质;“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容;(2)“阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任;tr1::shared_ptr支持定制型删除器。这可防范DLL问题,可被用来自动解除互斥锁等等。

# 第十九条 (1)Class的设计就是type的设计。在定义一个新type之前,晴确定你已经考虑过本条款所付款的所有讨论主题。

# 第二十条 (1)尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题;(2)以上规则并不适用于内置类型ÿ

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值