Effective C++ 笔记

This is a piece of individual notes of effect c++, which contains personal opinion and example.

签名,声明,定义

签名指代函数返回值+变量的类型,size_t (int)

声明指代函数的返回值、变量名、名称的说明,size_t rtt(int proj);

定义指代函数的具体内容,

1
2
3
size_t rtt(int a){
return sqrt(pow(a, 2)+pow(a, 2));
}

item 6: 必要时阻止拷贝,阻止默认构造/析构/拷贝行为

  • 若要编译期阻止拷贝/构造,将拷贝/构造函数写入 private 然后不实现(单例类常用实现)

    • 循环引用会导致non-local行为,使得单例也面临不一致问题。
  • derived class阻止拷贝,继承 uncopyable

    1
    2
    3
    4
    5
    6
    7
    8
    class uncopyable {
    protected:
    uncopyable() {}
    ~uncopyable() {}
    private:
    uncopyable(const uncopyable&);
    uncopyable& operator=(const uncopyable&);
    }

item 7: 多态基类声明virtual析构函数

  • 虚析构函数:用于多态基类

  • 虚析构函数防止derived class局部销毁:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class A {
    public:
    A() {}
    ~A() {}
    int AB;
    private:
    int a;
    }

    class B : public A {
    public:
    B() : A() {}
    ~B(){}
    private:
    int b;
    }

    // -----
    B* hi = new B;
    A* well = hi;
    delete well; // 只有 A 的部分会被析构,B 的private b 不会被析构!
    // 行为未定义,内存泄漏,数据(内存)结构破坏
  • 虚析构函数生成的vtbl会加大内存占用量:

    • 如果class不含析构函数,意味着不被用为一个base class
    • 当class中带有虚函数,才需要有虚析构函数
  • pure virtual: 纯虚函数,阻止初始化;

    • 构造纯虚类:纯虚析构函数,同时需要提供析构函数实现:最深层派生的class先被调用,然后才是base class。如果pure virtual destructor没有实现,则该类无法析构,连接器报错。

      1
      2
      3
      4
      5
      class AMOV {
      public:
      virtual ~AMOV() = 0;
      }
      AMOV::~AMOV() {}

item 8: 析构函数不可流出异常

  • 如果不需要处理,try...catch...,然后吸收或者结束程序
  • 如果需要处理,单独成立一个函数让用户handle异常

item 9

​ 构造/析构函数中,如果出现虚函数,被构造/析构的时候派生类会调用基类构造/析构实现。导致未定义错误。pure virtual函数同理。

item 10: operator=

​ operator=返回*this,可以使得行为与基础类型一致。

item 11 =的自我赋值

​ 解决 = 自我赋值问题:copy-and-swap

1
2
3
4
5
widget & widget::operator=(const widget& rhs) {
widget tmp(rhs);
swap(tmp);
return *this;
}

item 12 继承、拷贝问题

​ 继承-拷贝问题:派生类赋值需要考虑基类的数据继承问题,可以考虑用基类的operator=进行赋值操作。

1
2
3
4
5
class D : public B {
D(const D& d){
B::Operator=(d);
}
}

item 17

​ 独立生成new语句,放入智能指针,以避免顺序生成异常造成的内存泄漏。

1
2
3
4
5
6
7
8
9
10
void processWidget(std::share_ptr<int>(new int[12]), priority());
/*
如果new出错,priority已完成,那priority可能内存泄漏
如果priority出错,new完成但未进入share_ptr, new int[12]
将内存泄漏
*/

/* 正确示范 */
std::share_ptr<int> tmp(new int[12]);
void processWidget(tmp, priority());

item 19 ~ 25: 提高封装性,可用性,代码清晰度

  • 面向对象的三特性:封装性,继承性,多态性

  • 封装性越高,底层实现的灵活度越高;

  • 声明成员变量为private,并用getter、setter,以防止直接操作:成员变量描述内部状态,公共成员函数提供接口;

  • non-member non-friend 函数替代 member 函数:降低外部访问度,提高封装性

  • operator,等函数:对于类,pass-by-reference-to-const,减少可能的复制(c++17 有优化,编译器也会进行一定的优化)

  • argument dependent lookup: 对于不同命名空间内的函数,根据argument dependent lookup 原则,首先会调用本全局函数,再(自身)命名空间内的函数。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<typename T>
    void doS(T& l, T& r) {
    using namespace std;
    // ...
    swap(l, r);
    }

    // 编译器首先搜寻全局域,看是否有l,r的全特化;然后寻找l,r所属类所属命名空间,查找是否有
    // 全特化,最后再调用标准的swap.
  • 成员函数swap特化具有STL的一致性,但依旧需要提供非成员函数swap,以提高封装性与可操作性。(个人见解)

item 26:尽可能延后定义式的出现,避免不必要的构造/析构成本

当函数定义了一个对象后,会调用默认(或给定)构造函数,并在区域结束时会调用析构函数;一般情况下,非到对象初值确定,不对其进行定义;若在循环体中,应考量构造、析构、赋值的开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void doSTH(std::string a) {
/* 如果if内抛出异常,则b的构造、析构开销是无用的 */
std::string b;
if (a) {
// ...
}
return b;
}

// 方法1
Widget w;
for (int i = 0; i < n; ++i) {
w = impl;
// ...
}

// 方法2
for (int i = 0; i < n; ++i) {
Widget w(impl);
// ...
}

item 29: 异常安全 (important)

线程安全函数的保证:

  • 基本承诺:如果异常被抛出,程序内任何事物仍然处于有效状态。所有原有状态不改变。
  • 强烈保证:程序状态不改变:commit or rollback
  • nothrow保证:函数不抛出异常

异常安全具有随后性,如果一个函数中包含基本安全函数、强烈安全函数,那该函数的安全性为基本安全。

item 31: 编译壁垒

The PIMPL idiom

item 32-33: public inherit: is-a

继承关系,is-a;

避免derived class的作用域覆盖问题:

若derived class中复写base class中的方法,则基类同名方法不可用。

不管base class里是否virtual,都会因为覆写而被屏蔽。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class B {
private:
int a;
float b;
public:
void mt1();
void mt1(double);
}

class D : public B {
public:
void mt1();
}
////////
D dm;
dm.mt1() // dm.D::mt1()
dm.mt1(x); // 错误!overwrite!

解决方案:

1
2
3
4
5
6
class D : public B {
public:
using B::mf1; // 此处只能用mf1不能mf1(double),
// 名称与命名空间的引用。
void mf1();
}

关键字:using

另外方法:forwarding:

1
2
3
virtual void mf1() {
Base::mf1();
}

Item 34: 区分接口继承与实现继承

  • 成员函数的接口总会被继承
  • pure virtual func是为了继承接口
  • impure virtual func继承的同时提供了缺省实现
  • non-virtual func继承提供了强实现,不允许重写

接口有限继承,避免默认基类方法:(显式指定缺省方法)

  1. protected default function:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class A {
    public:
    virtual void echo() = 0;
    protected:
    defaultEcho() {
    std::cout << "echo::A" << std::endl;
    }
    }

    class B : public A {
    public:
    virtual void echo() {
    defaultEcho();
    }
    }
  2. instantiation for pure virtual function:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class A {
    public:
    virtual void echo() = 0;
    }

    void A::echo() {
    std::cout << "echo::A" << std::endl;
    }

    class B : public A {
    public:
    virtual void echo() {
    A::echo();
    }
    }

Item 37:virtual 函数参数缺省值问题

不要重复定义一个继承而来的缺省参数值,会导致派生类的缺省值失效。

动态类型与静态类型

动态类型绑定(dynamically bound)与静态类型绑定(statically bound)问题:声明中所采用的类型,成为静态类型;实际使用中的类型,称为动态类型。e.g.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Shape {
public:
virtual void echo(int i = 0) {
std::cout << i
<< "Shape"
<< std::endl;
};
// ...
private:
// ...
}

class Circle : public Shape {
public:
virtual void echo(int i = 2) {
std::cout << i
<< "Circle"
<< std::endl;
}
}

/**
* ps为Shape*类型,静态类型为Shape*
* ps指向了Circle,其动态类型为Circle
* ps调用virtual函数会使用Circle的实现
*/
Shape* ps = new Circle;

为提升运行期效率,在编译期决定缺省参数值C++会使用基类中virtual函数的缺省参数值。e.g.:

1
2
3
4
5
// 此命令会使用Shape提供的 i = 0;
ps->echo();
// 输出的结果是:
// 0Circle\n

为解决上述问题,并降低派生类中的代码重复,防止一个缺省值出现多次,可以采用item35提及的NVI方法或者其他替代方案。例如NVI,将实际运行函数置于private中,然后基类中使用non-virtual public函数调用private函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Shape {
public:
void echo(int i = 0) {
doEcho(i);
}
private:
virtual void doEcho(int i) {
//...
}
}

class Circle : public Shape {
public:
//...
private:
virtual void doEcho(int i) {
//...
}
}

Shape* ps = new Circle;
ps->echo(); // 此处使用的i = 0;

评论

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×