effective C++ 3rd 笔记(一)

条款01:视C++为一个语言联邦 : C, Object-Oriented C++, Template C++, STL

条款02:尽量以const,enum,inline 替换#define:

1.对于单纯常量,最好以const对象或enum替换#define

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

#define ASPECT_RATIO 1.653

const double AspectRatio = 1.653;

        1)用#define定义的名字可能没有进入记号表,编译出错,不方便追踪

        2)用const定义常量将得到更精简的目标代码(object code),因为预处理器处理#define是会盲目的将宏名称替换为1.653导致目标码出现多份1.653若用const则不会出现这种问题。

        当用常量替换#define有两种特殊的情况。

       1)定义常量指针。由于常量定义式通常放在头文件内(以便被不同源码含入),因此有必要将指针声明为const。

        const char* const authorName = “Scott Meyer”;

        string对象通常比char*-base合宜,所以authorName往往定义成这样:const std::string authorName(“Scott Meyer”);

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

class GamePlayer {

private:

   static const int NumTurns = 5;

    int scores[NumTurns];

    ...

}

通常static & const& 整数类型(ints,chars,bools)类型成员如果不取地址,可以声明并使用而无需定义。但如果你取某个class专属常量的地址,或你的编译器却(不正确地)坚持要看到一个定义式就必须提供如下定义式

const int GamePlayer::NumTurns;   //放到实现文件,因为在声明时已获得初值,不能再设初值。

我们无法用#define 创建class专属常量,因为#define不重视作用域scope。

旧时编译器也许不支持上述语法,不允许static成员在声明时提供初值。

则将初值放到定义式里,但编译器坚持必须在编译期间知道数组score[]大小,万一你的编译器不允许static 整数型class常量完成 in class初值设定,可改用 the enum hack补偿做法。

class GamePlayer{

private:

    enum{ NumTurns=5 };

    int scores[NumTurns];

}

1)取enum和#define地址一样,不合法。如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。enums和#defines一样不会导致非必要的内存分配。

2)实用,很多代码用了。 enum hack是 模板元编程template metaprogramming的基础技术。

 

另一个常见的#define误用情况是用它实现宏。宏看起来像函数,但不会招致函数调用带来的额外开销。

#define MAX(a,b) f((a)>(b)?(a):(b))
int a =5, b = 0;
MAX(++a, b);//a被累加二次
MAX(++a, b+10);//a被累加一次  

 

替换为内联模板函数:宏的效率和类型安全,可预料行为
template<typename T>
inline void MAX(const T&a,const T&b)
{
      f(a>b?a:b);
}

 

 

条款03: 尽可能实用const

const Rational operator* (const Rational& lhs, const Rational& rhs);//为什么返回const对象?

Rational a, b, c;

(a * b) = c;//

if(a * b = c) … // 其实想做比较操作,返回const可预防此类问题

 

const成员函数:可被const对象调用。两个函数如果只是常量性不同,是可以被重载。

可用mutable释放掉non-static成员变量的bitwise constness约束(即mutable修饰的成员变量可被const成员函数修改)。

 

在const和non-const成员函数中避免重复:

class TextBlock {

public: …

  const char& operator[] (std::size_t position) const {

   …

   return text[position];

}

char& operator[] (std::size_t position) {   //non-const成员函数调用const成员函数避免代码重复

return

          const_cast<char&>(

                  static_cast<const TextBlock&>(*this)

                       [position]);

}

private: std::string text';

}; 

 

不能令const成员函数调用non-const成员函数,对象有可能被改动。

 

条款04: 确定对象被使用前已先被初始化。

1.对内置类型手工初始化

2.构造函数最好使用成员初值列 member initialization list,而不要在构造函数体内用赋值操作。初值列中列出的成员变量次序要跟在class中声明次序一致。

3.为免除跨编译单元之初始化次序问题,用local static 对象替换non-local static 对象

class A {

public: A(const string&);

private:

            string str;

             int num;

}

A::A(const string &s){

str=s;   //赋值而非初始化,在初值列中调用一次default构造函数,函数体内一次赋值操作

num=0'; //内置类型不保证。

}

C++规定对象的成员变量初始化动作发生在进入构造函数体之前。如果成员变量是const或引用,则一定要初始化,而不能被赋值。

A::A(const string &s):str(s),   //值调用一次copy构造函数

                                       num(0){}  

static对象:从被构造到程序结束为止。main()结束时自动析构。

包括global对象,namespace内的对象,在class内、函数内、及在file作用域内被声明为static的对象。

local static对象:函数内的static对象

non-local static对象:其他

如果某个编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,则可能未被初始化。C++对”定义于不同便一单元内的non-local static对象“的初始化次序无明确定义。

class FileSystem {

public:  …

              std::size_t numDisks() const;

     };

extern FileSystem tfs;

//另一编译单元

class Directory {

public:

         Directory(params);

};

Directory::Directory(params) {

      std::size_t disks = tfs.numDisks();

}

Directory tempDir (params);

 

解决方案:singleton模式,用local static对象替换non-local static对象

C++保证,函数内的local static 对象会在该函数被调用期间,首次遇上该对象定义式时被初始化。

class FileSystem {…};

FileSystem& tfs() {

     static FileSystem fs;

     return fs;}

class Directory {…};

Directory::Directory (params) {

     std::size_t disks = tfs().numDisks();

};

Directory& tempDir() {

    static Directory td;

    return td;}

 

 

条款05:了解C++默默编写并调用哪些函数

编译器暗自为class创建default构造函数,copy构造函数,copy assignment操作符,以及析构函数

只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。

如果没有定义复制构造函数,编译器就会合成一个,即使已经定义了其他的构造函数。

template<class T>

class NamedObject {

public: NamedObject(string &name, const T &value):nameValue(name),objectValue(value){}

private: string &nameValue;

             const T objectValue;

};

string newDog(“aaa”);

string oldDod(“bbb”);

NamedObject<int> p(newDog,2);

NamedObject<int> s(oldDog, 36);

p = s; //编译失败

 

在内含引用或const成员的class内支持赋值操作符,你必须自己定义copy assignm操作符。

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

 

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

方案:将相应成员函数声明为private并不予实现。或使用Uncopyable这样的base class

明确声明一个成员函数,阻止编译器暗自生成,另其为private,阻止外部调用。

但成员函数和friend仍然可以反问,所以只声明而不定义它们,如不慎调用,则会得到一个连接错误。

 

将连接期错误移到编译器:

class Uncopyable{

protected: Uncopyable(){}              //允许derived对象构造和析构

                     ~Uncopyablel(){}

private: Uncopyable(const Uncopyable&);    //但阻止copying

               Uncopyable& operator=(const Uncopyable&);

};

class HomeForSale:private Uncopyable { …};

当尝试拷贝HomeForSale对象时,自动生成copy构造函数和copy assignment操作符,并尝试调用基类对于版本,但失败。。。

 

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

1.若果class带有任何virtual函数,它就应该拥有一个virtual析构函数

2.class的设计目的不是作为base class使用或不是为了具备多态性,就不改声明为virtual析构函数

 

C++明确指出当derived对象经过base class指针被删除,而该base class带有一个non-virtual析构函数,则其结果未定义的:局部销毁。

每一个带有virtual函数的class都有一个相应的由函数指针构成的数组vtbl(virtual table). 当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr (virtual table pointer)所指的那个vtbl,编译器在其中寻找适当的函数指针。无端声明析构函数为virtual会增加为vptr的付出的空间补偿。

class SpecialString: public std::string{   //馊主意!

};

SpecialString *pss = new SpecialString(“aaa”);

std::string *ps=pss;

delete ps;//未定义!

如果企图继承一标准容器或任何其他带有non-virtual析构函数的class,拒绝诱惑吧!!!

为你希望成为抽象(不能实体化)的那个class声明一个pure virtual析构函数

class AWOV {

public: virtual ~AWOV() = 0;

};

必须提供一份定义 AWOV::AWOV() {}

不然派生类的析构函数调用base class的析构函数时,不然连接器会报错。

 

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

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

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

栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。在执行析构函数前如果已经有一个异常,析构函数中又抛出一个异常,将会导致调用标准库 terminate 函数强制从程序非正常退出。

class DBConnection {

public : static DBConnection create();

              void close();

};

确保DBConnection不忘记调用close()

class DBConn {

public:

         ~DBConn(){

                     db.close();}

private: DBConnection db;

};

DBConn dbc(DBConnection::create());

如果析构函数抛出异常:

1) 强迫结束

DBConn::~DBConn(){

try { db.close();}

catch(…) {

          制作运转记录,记下对close的调用失败

          std::abort();

}

2)吞下异常

DBConn::~DBConn(){

try { db.close();}

catch(…) {

制作运转记录,记下对close的调用失败

}

3)较佳的策略,客户自己调用普通函数

class DBConn {

public:

void close(){

    db.close();

   closed = true;

}

~DBConn(){

    if(!closed) {

         try { db.close(); }

         catch(…){

                  制作运转记录,记下对close的调用失败

                 abort或吞下异常

                    }

    }

private: DBConnection db;

               bool closed;

};

 

 

条款09:绝不在构造和析构函数中调用virtual函数

因为这类调用从不下降至derived class (比起当前执行构造函数和析构函数的那层)

class Transaciton {

public:Transaciton();

         virtual void logTransaciton() const = 0;

};

Transaciton::Transaciton(){

       logTransaciton();

}

class BuyTransaciton:public Transaciton{

public:

         virtual void logTransaciton() const;

};

class SellTransaciton:public Transaciton{

public:

          virtual void logTransaciton() const;

};

BuyTransaciton b;  //基类构造函数调用的是Transaciton::logTransaciton();

BuyTransaciton专属成分还未初始化,最安全的做法就是视而不见,当成Transaciton类。

对象在derived class 构造函数开始执行前不会成为一个derived class。

析构函数类似,在进入base class 析构函数后对象就成为一个base class对象。

 

解决方案:令derived class 将必要的构造信息上传至base class构造函数,来替换无法使用virtul函数从base class向下调用。

class Transaciton {

public:

          explicit Transaciton(const string &loginfo);

          void logTransaciton(const string &loginfo) const;

};

Transaciton::Transaciton(const string &loginfo){

          logTransaciton(loginfo);

}

class BuyTransaciton:public Transaciton{

public:

          BuyTransaciton( parameters): Transaciton( createLogString(parameters)) {…}

private:

         static string createLogString(parameters);

};

 

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

条款11: 在operator= 中处理自我赋值

class Bitmap {…};

class Widget {

private: Bitmap *pb;

};

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

     delete pb;                            //没有进行自我赋值检测

     pb = new Bitmap(*rhs.pb);//不具备异常安全,发生异常后,pb指向一块已删除的内存

     return *this;

}

解决:证同测试

Widget& Widget::operator=(const Widget &rhs) {//不具备异常安全

        if (this == &rhs) return *this;

        delete pb;

        pb = new Bitmap(*rhs.pb);//不具备异常安全

        return *this;

}

 

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

        Bitmap *pOrig = pb;  //异常安全,又能处理自我赋值,当自我赋值发生频率较高时,可加入证同测试。

        pb = new Bitmap(*rhs.pb);

        delete pOrig;

        return *this;

}

另一替换方案确保异常安全,又能处理自我赋值:copy and swap技术

class Widget{

      void swap(Widget &rhs);

};

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

     Widget temp(rhs);

     swap(temp);

     return *this;

}

Widget& Widget::operator=(const Widget rhs){ //另一较高效写法,by-value

     swap(rhs);

    return *this;

}

 

条款12: 复制对象时勿忘其每一个成分

1.copying 函数应该确保赋值”对象内所有成员变量”及“所有base class成分”

2.不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个函数共同调用。

 

为derived class编写copying函数时,必须复制其base class成分。

class Customer {

public:

          Customer(const Customer &rhs);

          Customer& operator=(const Customer &rhs);

private: string name;

};

class PriorityCustomer: public Customer {

public:

          priorityCustomer(const priorityCustomer &rhs);

          priorityCustomer& operator=(const priorityCustomer &rhs);

private: int priority;

};

priorityCustomer::priorityCustomer(const priorityCustomer &rhs)

:Customer(rhs),  //调用base class的copy构造函数,如省略,则调用默认的构造函数,基类部分为默认值

priority(rhs.priority)

{ }

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

        Customer::operator=(rhs);    //可先进行证同测试,成员如有指针

        priority = rhs.priority;  

        return *this;

}

posted on 2011-04-12 16:29 Atela 阅读(237) 评论(0)  编辑 收藏 引用 所属分类: C/C++


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

统计

常用链接

留言簿

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜