Effective C++ 笔记

1、尽量以const,enum,inline替换#define

  对于变量, const 更容易定位问题, const 可能会使代码交少。

  相比 const char* const author = "Scott";     const std::string author("Scott"); 更好一些。

  宏函数更是难以理解,最好用template inline 代替。

2、尽可能使用const

char greeting[] = "Hello";
char* p = greeting;                    // non-const pointer, non-const data
const char* p = greeting;              // non-const pointer, const data
char* const p =greeting;               // const pointer, non-const data
const char* const p = greeting;        // const pointer, const data

 const(修饰data) char* const(修饰指针) p = greeting; // 以char* 为分界, 左边的修饰对象,右边的修饰指针。

std::size_t length() const;  //保证函数内部的常量性,内部变量不能进行赋值。(mutable可以使变量在const成员函数内被更改)

3、确定对象被使用前初始化

  在构造函数的初始化列表中进行初始化,而不是在构造函数内进行赋值。

4、明确拒绝不想使用编译器自动生成的函数。

  例如单例模式中,将 copy,copy assignment, operator= 定义成私有函数,并且只声明,不实现。

5、为多态基类声明virtual析构函数。

  当derived class对象经由一个base class指针被删除,而base class带着一个non-virtual析构函数,其结果使未定义的--其结果通常是derived成分没有被销毁。

  如果class不含virtual函数,通常表示它并不想作为case class, 当class不会被作为base class时,令其析构函数为virtual往往是个馊主意。

  virtual函数会增加对象的大小。将所有析构函数都声明为virtual,就想从未声明它们一样,都是错误的。 只有当class内含至少一个virtual函数,才声明virtual析构函数。

  pure virtual 函数,不能实例化的class,类似接口。 virtual ~AWOV()=0;

6、不要让异常逃离析构函数

  例如关闭数据库的动作,close() ,可以将其close()的操作放到析构函数中,但是如果close()产生异常,我们就没有办法处理了。可以提供一个bool close()接口,这样客户就可以知道close()是否成功,而做出相应处理。

7、不要在构造和析构函数中调用virtual函数。

8、以对象管理资源

  用智能指针管理内存,unique_ptr, shared_ptr。

  使用STL 中的容器(vector)代替array等。

class Lock {
public:
   Lock(Mutex *mutex):_mutex(mutex) { lock(_mutex); }
   ~Lock() { unlock(_mutex); }
private:
   Mutex *_mutex;
}

void test()
{
Mutex mutex;

{ //加锁部分
Lock(&mutex);
     ...
}
}

  当复制自身时, Lock m1(&mutex);   Lock m2(m1);  会发生什么? 

  我们可以进行完善, 1,禁止复制。 2,用智能指针管理_mutex;

9 、以独立语句将newed对象置于智能指针

processWidget(std::shared_ptr<Widget>(new Widget), priority());

  上面的代码可能会引起内存泄漏,主要原因是,我们无法确定, new Widget, priority(), shared_ptr构造函数,它们的执行顺序。如果先new Widget, 在执行 priority(),而在执行 priority()时,发生异常,就会产生内存泄漏。

10、让接口容易被正确使用,不易被误用。

  尽量使type的行为和内置type一致。

  返回一个 shared_ptr而非原始指针,这样可以阻止客户犯下内存泄漏的问题。

  shared_ptr 可以提供一个 deleter。(可以防止DLL问题,自动解锁互斥锁。)

11、以const 引用代替 值传递

  值传递会增加构造函数调用。

  引用对象还可以解决切割问题。

#include <iostream>

using namespace std;

class Base
{
public:
    virtual void Print(void) const {
        cout << "Base print." << endl;
    }
};

class Derived : public Base
{
public:
    virtual void Print(void) const {
        cout << "Derived print" << endl;
    }
};

void PrintClassByVale(Base value)
{
    value.Print();
}

void PrintClassByReference(const Base &value)
{
    value.Print();
}

int main(void)
{
    Derived der;

    PrintClassByVale(der);
    PrintClassByReference(der);

    return 0;
}
View Code

  对于内置类型,通常值传递更合适。

12、不可以返回局部对象的指针和引用。

13、尽量延后变量的定义。

  对于一个变量是定义在for循环外还是循环内有以下考虑:

  A:在 for 外部, 1个构造+一个析构+N个赋值。

  B:在 for 内部, N个构造+N个析构。

  如果 class 的一个赋值操作成本低于构造+析构, A的效率高于B,尤其时N比价大时。但是A的作用域更大,对程序的易维护性造成冲突。除非确认赋值成本比构造+析构的成本低并且代码对执行效率高度敏感,否则应该使用方案B。

14、尽量减少转型动作,使用新式转型。

  const_cast<T>(expression), 通常用来将对象的常量性转除。

  dynamic_cast<T>(expression),通常执行“安全向下转型”,也就是用来决定某个对象是否归属继承体系中的某个类型。(可能消耗重大运行成本,里面调用的类似strcmp的函数)

  reinterpret_cast<T>(expression),通常用于低级转型。

  static_cast<T>(expression),强迫隐式转换,non-const 转 const, int 转 double, void* 转 type指针, pointer-to-base 转 pointer-to-derived等。(无法将const转non-const)

  一种场景:

class A
{
public:
  void Print(void) { cout << "print A" << endl; } };
class AB : public A {
public:
  void Print(void) { cout << "print AB" << endl; }
};
class AC : public A 
{
public:
  void Print(void) { cout << "print AC" << endl; }
};
class AD : public A 
{
public:
  void Print(void) { cout << "print AD" << endl; }
};

void test(A *a) {
  if (nullptr == a) {
    return;
  }
  AB *ab = dynamic_cast<AB>(a);
  if (nullptr == ab) {
    return;
  }
}

我们想在一个容器中放 AB,AC,AD时,我们可以放 A的指针。 当我们把对象取出来时,再使用 dynamic_cast 进行转换。但是由于dynamic_cast会导致效率下降。我们考虑通过 virtual 来避免进行类型转换。

 15、避免返回handles指向对象内部成分。

#include <iostream>
#include <memory>

using namespace std;

class Point
{
public:
    Point(){}
    Point(int x, int y) : _x(x), _y(y) {}
    void set_x(int x) {_x=x;}
    void set_y(int y) {_y=y;}
private:
    int _x;
    int _y;
};

struct RectData
{
    Point ulhc;
    Point lrhc;
};

class Rectangle
{
public:
    Rectangle() {_data = new RectData();}
    ~Rectangle() {delete _data;}
    Point &UpperLeft() const {return _data->ulhc;}
    Point &LowerRight() const {return _data->lrhc;}
private:
    RectData *_data;
};

int main(void)
{
    const Rectangle rec;
    rec.LowerRight().set_x(5);

    return 0;
}
View Code

  rec 是 const 对象,本应该是不可改变的,但是上面的代码确可以编译。

    const Point &UpperLeft() const {return _data->ulhc;}
    const Point &LowerRight() const {return _data->lrhc;}
View Code

  在返回类型加上 const 可解决上面的问题。

  避免返回handles(包括references,指针,迭代器)指向对象内部,遵守这条规则,可增加封装性,帮助const成员函数的行为像个const,并且将客户拿到无效引用,指针的可能性将到最低(如果返回一个引用,指针,就要确保对象的生存时间比客户生存时间长。)

posted on 2021-03-06 16:53  gaozy6626  阅读(50)  评论(0编辑  收藏  举报