首页 > 代码库 > Effective C++ 55 Ways 读书笔记

Effective C++ 55 Ways 读书笔记

 

第一章: 适应c++ 

 

1. 相比较#define, 更倾向于用const, enum, inline。 

#define 在预处理阶段处理,在编译后的文件中找不到任何的symbol, 不利于debug. 而const, enum 则有。
相对于#define, inline 函数既也能减少函数调用的开销,也能做更多的类型检查,不易出错。

2. 尽可能多的使用const.

原因1:

X operator*(const X& x1, const X& x2);X a, b, c;if ((a*b)=c) // 合法,但通常不是我们想要的const X operator*(const X& x1, const X& x2);X a, b, cif ((a*b)=c) // 不合法, 正是我们想要的

 

原因2:编译器能对reference-to-const 做更多的优化, 而const 对象只能调用const 成员方法。 所以某个成员方法如果是const 的, 就标记他为const 的。

3 确保在使用某个对象时,这个对象已经被初始化了。

对于global 的指针变量,可能的实现方式是:

X* px;void f() {    px->f();}

更好的实现方式是:

X& getX(){    static X x; // init here, only once    return x;}void f() {    x.f();}

 

第二章:类的构造函数,析构函数,赋值函数

 

构造函数

4. 编译器会产生默认的构造函数,所以如果不希望类被拷贝构造或者赋值构造,就明确的禁用掉。

    class X {        private:            X(const X& x);            X& operator=(const X& x);    };

5. 不要在构造函数中调用virtual 成员函数。

    class Transaction {        public:            Transaction() { logTransaction(); }            virtual logTransaction() {}    };    class ATransaction: public Transaction {}; 
// 这里的A 调用的还是Transaction 的logTransaction() 函数。 因为A 的初始化是先从基类开始的。 

解决方法:

    class Transaction {        public:            Transaction(const std::string& loginfo) { logTransaction(loginfo);}            logTransaction(const std::string loginfo) {}  // 不再是virtual 函数    };    class ATransaction : public Transaction {        public:            ATransaction(params): Transaction(createLogString(params));        private:            static std::string createLogString(params);  // 这里设置成static, 也能确保createLogString 不能访问任何的成员变量。    };

 

类的析构函数

6. 多态编程,基类的析构函数都是virtual 的。 如果成员函数有virtual 的, 析构函数必须是virtual 的

   class Base { public: ~Base(){} };   class D1 : public Base {};   class D2 : public Base {};   Base* pb = getBase();  // 可能是D1, 也可能是D2   delete pb;  // 糟糕, 调用的Base::~Base();


正确的写法是:

class Base { public: virtual ~Base(){} };

7. 基类的析构函数不一定必须是virtual 的。 如果不必须,又设置成virtual, 反而会有损失。

   class Point {            int x;           int y;   };


本身这个类只有64bit, 可以完整的放到寄存器里面。 如果设置了virtual 的析构函数,因为多类vtable, 反而不能放在寄存器了。

8 将析构函数可能抛出exception的代码通过public 成员函数暴露给用户.

    class X {        ~X {            f(); // 可能抛出异常        };    };    void f() {        vector<X> vx;  // vx 里面的成员会被逐个析构        ...;    };

如果某个X::~X 抛出异常, 后面的类的析构函数就不能轻易调用。
可能的解决方式:

    class X {        ~X() {            try {                f();            } catch .. {                // log                // std::abort();            }        }    }; 

但这里的的问题是无论是否abort, 用户都不知道有这个异常。 更好的做法是:

    class X {        public:            void release() {                close();                closed = true;            }            ~X {                if(!closed) {                    try {                        close();                    } catch .. {                        // log here                    }                }            }        private:            bool closed;    };

这样用户就有机会去catch closed 的exception

{    X x;    try {        x.close();    } except .. {    }}

 

赋值构造函数

9 让赋值构造函数返回reference *this

X& operator(const X& x) { //...; return *this;}x1 = x2 = x3 = X(params); // 合法的。

10 赋值构造函数要确保传入的reference 不是reference 的自己。

有bug 的版本

class X {    public:        X& operator=(const X& x) {            delete p;  // 如果this 和 x 是同一个对象, delete p 时也delete 了x.p            this.p = x.p;            return *this;        }        P* p;    };

解决方法:

X& X::operator=(const X& x) {    if(this == &x) return *this;    delete p;    p = x.p;    return *this;};

 

Effective C++ 55 Ways 读书笔记