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, c
if ((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;
};

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。