effective c++ 读书笔记

本文深入探讨C++编程的关键原则,包括使用关键字explicit避免隐式类型转换,掌握const的多种用途,确保对象初始化,理解C++编译器的默认行为,以及如何正确管理资源。通过遵循这些指导方针,开发者可以提高代码质量,减少bug,和增强程序的可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.前言

1.关键字explict可以用来阻止构造函数执行隐式类型转换,声明为explict的构造函数通常比non-explict更受欢迎,

因为它们禁止编译器执行非预期(往往也不被期望)的类型转换。除非我有一个好理由允许构造函数被用于隐式类型转换,否则

我会把它声明为explict。

2. 

class Widget{
public:
     Widget();                             //default构造函数
     Widget(const Widget & rhs);           //copy构造函数
     Widget& operator=(const Widget & rhs) //copy assignment操作符
     ...
};
Widget w1;                 //调用default构造函数
Widget w2(w1);            //调用copy构造函数
w1=w2;                     //调用copy assignment操作符

"="语法也可以用来调用copy构造函数:

Widget w3=w2; //调用copy构造函数!

“copy构造”和“copy赋值”有所不同。如果一个新对象被定义(如w3),一定会有个构造函数被调用,不可能调用赋值操作。如果没有新对象被定义(如“w1=w2”语句),就不会有构造函数被调用,那么当然就是赋值操作被调用。

3.由于各种因素,某些c++构建的行为没有定义;你无法稳定预估运行期会发生什么,这种就是所谓的“不明确行为”。如:

int *p=0;                 //p是个null指针
std::cout<<*p;            //对一个null指针取值会导致不明确行为

有时执行正常,有时造成崩坏,有时更产出不正确的结果。

4.stl分为容器、迭代器和算法三部分。

5.定义式是编译器为对象拨发内存的地点。

二.各个条款

01:视c++为一个语言联邦

包括:c、面向对象c++、模板c++、stl

02:尽量以const,enum,inline替换#define(宁可以编译器替换预处理器)

1)class专属常量。为了将常量的作用于限制于class内,你必须让它成为class的一个成员;而为确保此常量至多只有一份实体,

你必须让它成为一个static成员。

classs GamePlayer{
private:
       static const int NumTurns=5;       //常量声明式
       int scores[NumTUrns];             //使用该常量
};

2)取某个class专属常量的地址,或纵使你不取其地址而你的编译器却(不正确地)坚持要看到一个定义式,你就必须另外提供定义式如下:

const int GamePlayer::NumTurns;     //NumTurns的定义式

请把这个式子放进一个实现文件而非头文件。由于class常量已在声明式获得处置(如上NumTurns),因此定义式不可以在设初值。

3)宏看起来像函数,但不会招致函数调用带来的额外开销。多数情况不要使用宏。

4)可以使用template inline来代替宏。

template<typename T>
inline void callWithMax(const T&a,const T& b){
    f(a>b?a:b);
}

   5)对于单纯常量,最好以const对象或enums替换#define,对于形似函数的宏,最好改用inline函数替换#define

03:尽可能使用const

1)如果const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

2)令函数返回一个常量值,往往可以考虑降低因客户错误而造成的的意外,而又不至于放弃安全性和高效性。如:

class Rational{...};
const Rational oprator *(const Rational& lhs,const Rationsl& rhs);

    一个“良好的用户自定义类型”的特征是它们避免武断地与内置类型不兼容                                                                                        3)const成员函数的目的,是为了确认该成员函数可作用于const对象身上。

第一,它们使class接口比较容易被理解。得知哪个函数可以改动对象内容而哪个函数不行。

第二,它们使“操作const对象”成为可能。

4)两个成员函数如果只是常量性不同,可以被重载。                                                                                                                      5)mutable释放掉no-static成员变量的bitwise constness 约束:

mutable std::size_t textLength;    //这些成员变量可能总是会被更改,即使在const成员函数内。
mutable bool legthIsValid;

 6)就一般守则而言,转型是一个糟糕的想法。代码重复也不是什么令人愉快的经验。

7)将某些东西声明为const可帮助编译器侦测出错误用法。const可施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。                                                                                                                                                                                          8)编译器强制实施bitwise constness,但你编写程序是应该使用"概念上的常量性"(conceptual constness)。

9)当const和non-const成员函数有着实质性等价的实现是,令non-const版本调用const版本可避免代码重复。

04:确定对象被使用前已先被初始化  (读取未初始化的值会导致不明确的行为)                                                                           1)c++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。成员变量被赋值,出事化发生在这些成员的default构造函数被自动调用之时。

   2)构造函数的一个较佳写法是,使用所谓的member initialization list(成员初值列)替换赋值动作,如:

ABEntry::ABEntry(const std::string& name,const std::string& address,
                 const std::list<PhoneNumber>&phones):theName(name),//现在,这些都是初始化
                                                      theAddress(address),
                                                      thePhones(phones),
                                                      numTimesConsulted(0){}
//现在构造函数本体不必有任何动作。

上面这种写法初值列中针对各个成员变量而设的实参,被拿去作为各成员变量黄子健构造函数的实参。本例中的theName、theAddress、thePhones 以name、address、phones为初值进行copy构造。                                                                                  3)请立下一个规则,规定总是在初值列中列出所有成员变量,以免还得记住那些成员变量可以无需初值。

4)如果成员变量是const或references,它们就一定需要初值,不能被赋值。为避免需要继续成员变量合适必须在成员初值列中初始化,何时不需要,最简单的做法就是:总是使用成员初值列。

5)基类成员更早于派生类被初始化,而class的成员变量总是以其声明的次序被初始化                                                                   6)所谓static对象,其寿命从被构造出来直到程序结束为止。函数内的static对象称为local static对象,其他static对象称为non-localstatic对象。程序结束时会被自动销毁,也就是它们的析构函数会在main()结束时被自动调用。

7)所谓编译单元,是指产出单一目标文件的那些源码。基本上它是单一源码文件加上其所含入的头文件。   

  8)c++对“定义于不容的变异单元内的non-local static 对象”的初始化相对次序并无明确定义。解决办法:将每个non-local

static 对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。c++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。

9)这些函数“内含static对象”的事实使它们在多线程系统中带有不确定性。解决办法:在程序的单线程启动阶段手工调用所有reference-returning函数,这可消除与初始化有管的“竞速形势”。 

10)为内置型对象进行手工初始化,因为c++不保证初始化它们;

构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同;

为免除“跨编译单元之初始化次序”问题,请以locla static 对象替换non-local static对象。

05 了解c++默默编写并调用哪些函数       

1)default构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,像是调用base classes和non-static成员变量的构造函数和析构函数;至于copy构造函数和copy assignment操作符,编译器创建的版本只是单纯地将来源对像的每一个non-static成员变量拷贝到目标对象。                                                                                                                                                         2)如果打算在一个“内含reference成员”的class内支持赋值操作,你必须自己定义copy assignment操作符。                                  3)c++不允许“让reference改指向不同对象”。

4)如果某个base classes将copyassignment操作符声明为private,编译器将拒绝为其derived classes生成一个copyassignment操作符。

06 若不想使用编译器自动生成的函数,就该明确拒绝。

  1) 如 copy构造函数和copy assignment操作符都被声明为private而且没有定义

class HomeForSale{
public:
     ..
private:
    ..
    HomeForSale(const HomeForSales&);         //只有声明
    HomeForSale& operator=(const HomeForSale&);
};

    2)如果不慎在member函数或者friend函数之内那么做,轮到连接器发出抱怨。

解决办法:

 

class HomeForSale:private Uncopyable{      //class 不在声明copy构造函数或copyassign操作符
                                     
};
class Uncopyyable{
protected:
     Uncopyyable(){}                 //允许derived对象构造和析构
     ~Uncopyable(){}
private:
     Uncopyable(cosnt Uncopyable&);       //但阻止copying
     Uncopyable& operator=(const Uncopyable&);      

};

注:现在c++ 11可以试用delete

3)为驳回编译器自动提供的功能,可将相应的成员函数声明为private并且不予实现。试用像Uncopyable这样的base class也是一种做法。

07:为多态基类声明virtual析构函数 

1)为防止内存泄露     

 2)如果class不含virtual函数,通常表示它并不意图被用作一个base class。当class 不企图被当做base class,令其析构函数为virtual往往是个馊主意。                                                                                                                                                                     3)声明一个抽象类,并声明一个纯析构函数如:

virtual ~AWOV()=0 ;

    必须为这个纯析构函数提供一份定义,

如:

AWOV::~AWOV(){}

析构函数的运作方式是,最深层派生的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。编译器会在AWOV的derived classes的析构函数中创建一个对~AWOV的调用动作,所以你必须为这个函数提供一份定义。如果不这样做,连接器会发出抱怨。

08:别让异常逃离析构函数

 1)析构函数绝对不要吐出异常。如果一个析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。

2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该函数。

09:绝不在构造和析构过程中调用virtual函数     (没大看懂)                                                                                                                                                                                                                                                                                                                        在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。

10:令operator=返回一个reference to *this                                                                                                                                     

Widget & operator=(const Widget& rhs){    //返回类型是个reference,指向当前对象
                                         
    .....
    return *this;                         //返回左侧对象
 
}

         适用于所有赋值相关运算(+=,-=,*=)

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

1)当尝试自行管理资源(如果打算写一个用于资源管理的class就得这样做),可能会掉进“在停止使用资源之前意外释放了它”的陷阱。

解决办法:“证同测试”达到“自我赋值”的检验目的。

Widget& Widget::operator=(const Widget& rhs){

        if(this==&rhs)            //证同测试,如果是自我赋值,就不做任何事。
            return *this;
        delete pb;
        pb= new Bitmap(*rhs.pb);
        return *this;
}

一个常见而够好的operator=撰写方法,所以值得看看其实现手法:

class Widget{
    ....
    void swap(Widget& rhs);       //交换*this数据和上述复件的数据交换
    ....
}
Widget& Widget::operator=(const Widget& rhs){

     Widget temp(rhs);          //为rhs数据制作一份复件(副本)
     swap(temp)              //将*this数据和上述复件的数据交换
     return *this;
}

2)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。

3)确保任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

12:复制对象时勿忘其每一个成分                                                                                                                                                      1)如果你为class添加一个成员变量,你必须同时修改copying函数。以及任何非标准形式的operator=。如果忘记,编译器不太可能提醒你。

2)copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。

3)不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数,并由两个copying函数共同调用。                                                                                                                                                                                                                  资源管理:所谓资源就是,一旦用了它,将来必须还给系统。如果不这样,糟糕的事情就会发生。

c++最常使用的资源就是动态分配内存,其他的还有文件描述器、互斥锁、图形界面中的字型和笔刷、数据库连接、以及网络socket。                                                                                                                                                                                              13:以对象管理资源

1)获得资源后立刻放进管理对象内。

2)管理对象运用析构函数确保资源被释放。                                                                                                                                    3)为防止资源泄露,使用智能指针(如boost),它们在构造函数中获得资源并在析构函数中释放资源。

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

1)并非所有的资源都是heap-based,对那种资源而言,像auto_ptr和shared_ptr这样的智能指针往往不适合作为资源掌管者。有时候你需要建立自己的资源管理类。如:

class Lock{
public:
   explict Lock(Mutex* pm):mutexptr(pm)
    {lock(mutexptr);}             //获得资源
   ~Lock(){ unlock(mutexPtr);}      //释放资源
private:
    Mutex *mutexPtr;


};

   2)资源取得时机便是初始化时机(RAII)

3)当一个RAII对象呗复制,会发生什么事。大多数有两种解决办法:

   1.禁止复制。许多时候允许RAII对象被复制并不合理。(对一个像Lock这样的class这是有可能的)。如果不合理,便应该禁止。如:

class Lock:private Uncopyable{
public:                                       //禁止复制。见条款6。
};

  (或者使用=delete,c++ 11)

对底层资源祭出“引用计数法”。

未完待续!                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值