`

Effective C++》读书笔记

    博客分类:
  • c++
阅读更多
《Effective C++》读书笔记01:视c++为一个语言联邦

1.c语言;

2.经典的面向对象c++:包括了class,封装,继承,多态,虚函数等经典的OO思想;

3.c++模板编程,也称为泛型编程,这一部分相对内容较新,我个人正在学习《C++ Templates》加深理解;

4.STL:vector,list,map,iterator相信大家都很熟悉了吧:)

你看,这里的每一个部分都值得你花上半年的时间去理解,那些所谓的《XX天精通C++》根本就不可能实现,学好C++需要的是不断的积累和实践,希望等我毕业的时候能说自己精通C++了吧。


《Effective C++》读书笔记02:用const,enum,inline减少#define出场机会

以前学习c语言时,老师教导我们,对于数字最好用宏(#define)来定义它。以至于一直习惯于#define Age 12这样的写法。

在c语言的环境中,这确实是增加程序可读性的有效做法,可是在c++中,可以有更优美的实现方式。

1.当你写下

#define NUMBER 11.12

如果接下来使用NUBMER时出现了编译错误,那么由于在预处理过程中NUMBER已经被替换为11.12了,编译器指出错误发生在11.12上,如果使用者不是你,肯定会对11.12感到莫名其妙,要花不少时间追踪它。

替换方法有:

const double Number = 11.12;

这样一来,错误定位肯定没有问题了,而且如果多次使用这个常量,宏替换会产生多份11.12,而后者保证只有一个副本。

2.我以前写过这样的宏替换代码:

#define MAX(a, b) ((a) > (b) ? (a):(b)) //得到两个数中的最大值

在每个变量外都加了括号以后,使用起来确实没碰到过什么问题。可是作者这样使用这个函数:

int a = 5, b = 0;
MAX(++a, b); //a会被递增两次
MAX(++a, b+10); //由于b+10>a,a只被递增一次!!

会出现很大的问题!

在c++中,可以使用inline函数来解决:

template<typename T>
inline T MAX(const T& a, const T&b)
{
  return a > b ? a : b;
}

使用reference-to-const可以确保不对对象进行修改;而inline函数本身可以在编译时进行优化,提高编译速度。

Note:

1.对于一般常量,最好用const和enum替换#define;

2.对于类似函数的宏,最好改用inline函数替换#define。

《Effective C++》读书笔记03:多才多艺的const

const是我们写c++代码时的常客,对于那些我们不希望修改的对象,最好用const进行修饰。

1.下面来看看一些惯用法:
1 char greeting[] = "Hello";
2 char* p = greeting;            //非const指针,非const数据
3 const char* p = greeting;      //非const指针,const数据
4 char const *p = greeting;      //非const指针,const数据
5 char* const p = greeting;      //const指针,非const数据
6 const char* const p = greeting; //const指针,const数据



3和4行虽然形式不同,不过功能是类似的。

3和5有本质的不同,可以这么理解:const会修饰在它后面所有的代码,比如第3,4中的const修饰char* p,表示char*,而在第5中const修饰p,表示指针。

可这些在STL中有所不同,由于iterator是一个指针,用const修饰一个iterator会类似于上面第3行,而如果要产生第5行的效果,需要使用const_iterator:
1 using namespace std;
2 
3 vector<int> vec;
4 const vector<int>::iterator iter = vec.begin();//类似于T* const
5 *iter = 10; //正确,可以修改iter所指的值
6 ++iter; //错误,iter是const
7 
8 vector<int>::const_iterator cIter = vec.begin();//类似于const T*
9 *cIter = 10; //错误,cIter指向的值是const
10 ++cIter; //正确


总结一下:在一般的应用中const会修饰其后的变量和修饰符,而只有在STL的iterator中,const iterator==T* const;const_iterator==const T*。

记住,尽可能地将不能修改的变量声明为const!



2.在成员函数中使用const时,如果将成员函数声明为const,则函数中任何对任何成员变量进行修改都会导致错误,这样可以防止我们对对象无意的修改
1 class TestBlock
2 {
3  public:
4    void ConstFunc() const;//const函数
5  private:
6    char* text;
7    int size;
8    bool isValid;
9 }
10 
11 void TestBlock::ConstFunc() const
12 {
13  if(!isValid)
14  {
15    size = strlen(text);//错误
16    isValid = true;//错误
17  }
18 }


可以通过将成员变量声明为mutable(可变的)来消除这种错误

1 mutable int size;
2 mutable bool isValid;



3.const可用于重载,如果两个函数参数完全一致,可以通过将返回值声明为const或将函数声明为const来进行重载,不过在实现的时候,可以用非const函数来调用const函数,从而减少代码重复。这一块感觉用不太多,暂时就不管了。

《Effective C++》读书笔记04:确保对象在使用之前被初始化
我自己在写代码的时候也经常会遇到忘记初始化某对象的问题,而且这些错误比较难以调试,Meyers提出了一些避免这些错误的解决方法:

1.手工初始化所以内置类型:

这一条很好理解,对于int,enum等内置类型,在使用前一定要初始化。


2.对于类类型等用户自定义的对象,使用成员初值列初始化所有的对象:

1 using namespace std;
2  
3 class PhoneNumber{};
4 class Customer
5 {
6 public:
7    Customer(const string& name, const string& address, 
8            const PhoneNumber& phone);
9 private:
10    string theName;
11    string theAddress;
12    PhoneNumber thePhone;
13    int usedTimes;
14 }


对于Customer类的构造函数定义,一般我们会这么写:

Code

1 Customer::Customer(const string& name, const string& address, const PhoneNumber& phone)
2 {
3  theName = name;      //这些都是赋值
4  theAddress = address;//而不是初始化
5  thePhone = phone;
6  usedTimes = 0;
7 }


可是,在c++中,对不是内置型的对象的初始化都发生在进入构造函数之前,也就是说,在进行theName = name;赋值之前,theName就已经进行了初始化了,这个过程调用自己的默认构造函数。

紧接着有立刻进行了赋值操作,这样会造成额外的浪费,所以我们可以这样写构造函数:

1 Customer::Customer(const string& name, const string& address, const PhoneNumber& phone)
2  :theName(name),//成员初始化列
3    theAddress(address),
4    thePhone(phone),
5    usedTimes(0)//内置类型也一并初始化
6 {
7 }


使用了成员初始化列的方法,在进入构造函数体之前就进行了初始化,减少了赋值的开销,同时为了保持一致性,将内置类型也一并进行了初始化。

还有一点要记住:在成员初始化列中对变量的初始化次序是按照变量声明的次序的,也就是说,即使将上面的次序任意改变,也改变不了初始化次序,所以我们要尽可能地按照使用的顺序来声明变量!



3.在多个编译单元内的non-local static对象的初始化次序问题:

non-local static对象表示在程序执行过程中一直存在的对象,像类中声明的static变量,全局变量,而在普通函数中声明的static变量称为local static变量。

那么当有多个不同的编译单元(即存在于不同的文件中)时,对这些non-local static对象的初始化次序,在c++中,是不确定的,而且也没法确定!

当两个或多个文件中的non-local static对象发生关联时,问题就出现了。

解决方法就是使用了设计模式中的:Singleton单件模式,将对non-local static的访问移到函数中,将其转变为local static变量,确保其被初始化了再使用。

如果是多个non-local static对象互相之间都有关联,那。。对不起,是设计出了问题。

《Effective C++》读书笔记05:c++默默为您编写的函数
这一部分的条款讲的都是类的构造/析构/赋值函数的使用。

当你写下一个:

1 class Empty {};

经过了编译器的处理,就好像你写下了如下的代码:

1 class Empty
2 {
3 public:
4  Empty() {}                          //default构造函数
5  Empty(const Empty& rhs) {}          //copy构造函数
6  ~Empty() {}                          //析构函数
7 
8  Empty& operator=(const Empty& rhs) {}//copy assignment操作符
9 }


你看,c++编译器会在你需要的时候创建

1.default构造函数

2.析构函数

3.copy构造函数

4.copy assignment函数

这样一来,你就可以写如下代码了:
1 Empty e1;    //调用了default构造函数
2 
3 Empty e2(e1); //调用了copy构造函数
4 e2 = e1;      //调用了copy assignment函数
5                //调用析构函数

好吧,知道了有这些函数,可这些函数用来干什么?为什么编译器要写这些函数?

1.default构造函数和2.析构函数主要是给编译器一个放置代码的地方,可以用来调用基类和non-static成员变量的构造函数和析构函数。

3.copy构造函数和4.copy assignment函数,编译器创建的版本只是简单的将每一个non-static成员变量拷贝到目标对象,看下面这个例子:
1 using namespace std;
2 
3 class NameObject 
4 {
5 public:
6  NameObject(const char* name, const int& value);
7  NameObject(const string& name, const int& value);
8 
9 private:
10  string nameValue;
11  int objectValue;
12 }
13 
14 NameObject no1("Smallest Prime Number", 2);
15 NameObject no2(no1);  //以no1.nameValue和no1.objectValue的值设定no2的值
16                          //nameValue是string,首先会调用string的copy构造函数并以no1.nameValue作为参数
17                          //由于objectValue是内置int型,所以会直接将它的每一位拷贝过去。

上面的例子是理想的情况,就是每个变量都是可以直接赋值过去的,没有引用类型和const类型,假如有这两种类型的成员变量,则会报错


1 class NameObject
2 {
3 public:
4        NameObject(string& name, const int& value);
5 private:
6        string& nameValue;        //引用类型 
7        const int objectValue;    //const类型          
8 };
9 
10 int main()
11 {
12  string newDog("DaHuang");
13  string oldDog("XiaoGuai");
14  NameObject p(newDog, 2);
15  NameObject s(oldDog, 36);
16  p = s;          //错误,不能更改non-static的引用成员的指向,不能更改const成员的值
17                      //所以编译器提示不能 使用default assignment,并报错 
18  return 0; 
19  system("PAUSE");    
20 }



所以,在存在这样的成员变量时,尽可能自己定义coyy构造函数和copy assignment函数

《Effective C++》读书笔记06:如果不要编译器自动生成的函数,就明确拒绝
有时候,我们需要定义一些独一无二的类,这些类是如此的特别,以至于我们不能将其拷贝或者赋值:

1 class Family {};
2 
3 Family mine;
4 Family your;
5 Family her(mine);//不能通过编译
6 mine = your;    //也不能通过


可是上一个专题讲到,即使我们不定义copy构造函数和copy assignment函数,编译器还是会自动定义它们,所以,为了阻止上述行为,我们一般的解决方法就是:


1 class Family
2 {
3 public:
4  
5 private:
6  Family(const Family&);              //我们将其定义在private中
7  Family& operator=(const Family&);  //并且故意不实现它们:)
8 }


这样,如果用户无意中调用它们,就会在编译期间得到错误。而如果你在member函数和friend函数中调用,则会是连接器发出抱怨;

也可以将连接期间的问题移到编译期间,可以这么做:


1 class Uncopyable 
2 {
3 protected:
4  Uncopyable() {};
5  ~Uncopyable() {};
6 private:
7  Uncopyable(const Uncopyable&);            //阻止copy
8  Uncopyable& operator=(const Uncopyalbe&);  //阻止
9 };
10 
11 class Family: private Uncopyable //继承以后就完成了
12 {
13  
14 };

《Effective C++》读书笔记07:为多态基类声明virtual析构函数
这个问题在实践中偶尔会碰到,设计一个TimeKeeper基类和一些派生类来记录时间:

1 class TimeKeeper
2 {
3 public:
4  TimeKeeper();
5  ~TimeKeeper();
6  
7 };
8 
9 class AtomicClock: public TimeKeeper {}; //原子钟
10 class WaterClock: public TimeKeeper {}; //水钟


在使用时,我们可能会使用factory工厂方法:

1 TimeKeeper* getTimeKeeper();//返回一个指针,指向一个派生类的动态分配的对象
2
3 TimeKeeper* ptk = getTimeKeeper();//从继承体系中得到一个动态分配对象
4
5 delete ptk;//负责的删除它
删除的时候就会出现问题,因为ptk这个指针指向的是基类,那删除的指令会执行基类TimeKeeper的析构函数,该函数不是virtual函数。

在c++中,这样的情况下其删除行为没有被定义,一般会只删除基类的成分,而派生类的那些元素没有被删除,这就是形成资源泄露,败坏数据结构,在调试器上浪费很多时间的绝佳途径哦(引用原文翻译)。

解决的方法就是定义一个基类的virtual析构函数,这样一来,删除行为就会在派生类中实现,不会只删除一部分。

一般来说,只要类中有virtual函数,就要定义一个virtual析构函数。不过,如果类中没有virtual函数,就不需要也不应该定义virtual析构函数,这样不仅没用,而且也会增加额外开支,且会产生很多的兼容性问题,因为virtual机制是c++特有的。

另外,c++中很多类的实现都是不带virtual的,比如:string,STL中的vector,list,set,trl::unordered_map,如果继承它们很可能出现上述的错误,所以作者提醒大家:拒绝继承标准容器或者其它只有非virtual析构函数的类!

《Effective C++》读书笔记 08:别让异常逃离析构函数
这节和异常有关,这一块是我不太熟悉的,只能先把自己理解的记录下来。

1 class Widget
2 {
3 public:
4  
5  ~Widget() {} //假设这里会吐出一个异常
6 };
7 
8 void doSomething()
9 {
10  std::vector<Widget> v;
11  
12 }//v在这里自动销毁


上面的代码中,假设v含有10个Widget,如果在前面几个的析构函数中弹出异常,则程序会过早结束或者出现不明确行为。

确实不鼓励在析构函数中抛出异常,可是如果程序在析构函数中必须执行一个动作,而该动作可能会在失败时抛出异常,该怎么办呢?比如下例:


1 class DBConnection
2 {
3 public:
4  static DBConnection create();
5  void close();//关闭连接,失败则抛出异常
6 };


为了确保用户不忘记调用close()关闭连接,我们可以创建一个管理DBConnection资源的类:


1 class DBConn
2 {
3 public:
4  ~DBConn()
5  {
6    db.close();
7  }
8 private:
9  DBConnection db;
10 };


这样在使用时,如果close没有异常,则会很完美,不然DBConn就会使得它离开close函数,这会出现上述问题。

我们可以在DBConn的析构函数中,自己提前处理这个异常,但是这么做对于“导致close抛出异常”的情况无法做出反应。

一个比较好的策略是重新定义DBConn接口,给用户一个机会自己处理这种异常,比如,给用户定义一个函数close:

1 class DBConn
2 {
3 public:
4  void close()//让用户有机会自己捕捉异常
5  {
6    db.close();
7    closed = true;
8  }
9 
10  ~DBConn()
11  {
12    if(!closed)
13    {
14      try{
15        db.close();
16      }
17      catch(){
18        //记录下对close的调用失败
19      }
20  }
21 private:
22  DBConnection db;
23  bool closed;
24 };


这样一来,就有了双保险,用户可以自己处理异常,如果他们不处理,则析构函数会自动吞下异常。

总结:

1.在析构函数中尽可能不要吐出异常,如果真要吐出就在析构函数中捕获所以的异常,并提前结束程序或吞下它们;

2.如果用户需要自己处理异常,则在类中应该提供一个普通函数处理。
分享到:
评论

相关推荐

    effective c++读书笔记

    effective c++读书笔记和总结 effective c++读书笔记和总结

    effective C++读书笔记

    effective C++读书笔记,effective C++读书笔记,effective C++读书笔记

    effective C++ 读书笔记

    effective C++ 很好 很不错 最好的C++进阶资料

    effective C++ 读书笔记 PPT

    摘录了《Effective C++》 (Scott Meyers 著)中有参考价值的编写代码建议,方面阅读

    Effective C++学习笔记

    Effective C++学习笔记 没有时间看Effictive C++书的人可以看看这个笔记 总结出了重点啊 适合有c++基础的人阅读

    Effective Modern C++学习笔记

    《Effective Modern C++:改善C++11和C++14的42个具体做法(影印版)(英文版)》中包括以下主题:剖析花括号初始化、noexcept规范、完美转发、智能指针make函数的优缺点;讲解std∷move,std∷forward,rvalue引用和全局...

    Effective C++学习笔记.doc

    自己整理的侯捷版本&lt;&lt;Effective C++ 改善程序与设计的55个具体做法&gt;&gt; 学习笔记.

    Effective C++第三版及详细笔记

    内容简介:有人说C++程序员可以分为两类,读过Effective C++的和没读过的。当您读过《Effective C++:改善程序与设计的55个具体做法(第3版)(中文版)(双色)》之后,就获得了迅速提升自己C++功力的一个契机。

    effective c++笔记1

    effective c++笔记1

    Effective C++笔记1

    Effective C++笔记1

    Effective C++ Roy T 读书笔记。

    Effective C++ Roy T 读书笔记,比较老的一本书,探讨一下C++。

    Effective_C++_3rd笔记.pdf

    Effective_C++_3rd 读书笔记 pdf

    effective C++ (3rd) 学习笔记

    经典书籍 effective c++的学习笔记,一共31页,总结的很好,基本上是把原书的精华内容再加上自己的理解写在了这份文档中,适合打印出来细看。

    Effective_C++_笔记

    通常C++要求你对所使用的任何东西提供一个定义式,但如果它是个class专属常量 又是static且为整数类型(integral type,例如int,char,bool),则可特殊处理。只要不取它们 的地址,你可以声明并使用它们而无需提供...

    C++Effective STL笔记

    C++ Effective STL笔记,供C++初学者学习参考。。。。。

    C++书籍阅读笔记

    深入探索C++对象模型,Effective STL,more effective c++,C陷阱与缺陷,都是阅读笔记

    Effective C++中文版

    Effective C++_中文版. 数据C++中的高级进阶类书籍,本文档可以复制,非影印版

    c++笔记学习心得笔记

    c++笔记学习心得笔记,年多的C++学习过程中,自己阅读了很多C++经典著作,有《effective c++》,《more effective c++》,《c++ primer》等,每次阅读著作都会总结一些心得,现在拿出来和大家分享一下。

Global site tag (gtag.js) - Google Analytics