读书笔记(一)

条款03 尽可能使用const

  • 分清顶层const和底层const的区别
顶层const指的是const在*号的右边,表示指针本身是一个常量,无法修改对象本身.
底层const指的是const在*号的左边,表示的则是指针所指的对象是一个常量, 无法修改其指向的对象
对于内置数据类型来说const无所谓底层(基本类型没有指向某个对象的意思)的概念,引用本身不是对象
所以也无所谓顶层const,只有指针是比较特殊的。具有顶层const含义和底层const的含义
  • 两个成员函数如果只是常量性不同,可以被重载
  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复
class Test
{
  public:
    //至于为什么要有non-const和const两个版本这是因为bitwise constness 和 logical constness两个流行概念
    const char& operator[](std::size_t position)const {
        //some code 
        return data[position]
    }

    //non-const版本调用const版本,避免代码重复
    char& operatorp[](std::size_t position) {
        //some code
        return const_cast<char&>(
            static_cast<const Test&>(*this)[position]
        );
    }

  private:
    char data[MAX_SIZE];
};
  • bitwise constness 和 logical constness
bitwise constness流派认为成员函数只有在不更改对象中的任何成员变量(static除外)时才可以说是const的
logical constness流派主张一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出来的情况下通过给bitwise constness成员函数所要修改的变量加上mutable修饰,就变成了logical constness
  • const可以在类内部初始化静态成员
class Test
{
    public:
    private:
        const static int data = 10; //可以初始化
        //static int data = 10; 错误的,无法在类内部初始化
};

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

C++对于定义不同编译单元内的non-local static对象的初始化次序是未定义的,但是C++保证函数内的local static对象会在函数调用期间首次遇到该对象的定义式时被初始化,那么如果使用函数调用(返回内部local static对象)替换直接访问non-local static对象就可以解决这个未定义的问题,具体细节详见non-local static对象初始化顺序对于构造函数最好使用成员初值列表,而不要再构造函数本体内使用赋值操作.处置列表列出的成员变量,其排列次序应该和它们在class中的声明次序相同

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

  • 将相应的成员函数声明为private并且不予实现
  • 使用像Uncopyable这样的base class
class Uncopyable {
protected:
    Uncopyable() {}
    ~Uncopyable() {}
private:
    Uncopyable(const Uncopyable&);
    Uncopyable& operator=(const Uncopyable&);
};
通过继承Uncopyable就可以阻止编译器生成默认的拷贝赋值函数.
  • 使用boost库提供的noncopyable

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

绝对不能在析构函数中抛出异常,为了避免这个问题可以有以下几个办法避免.

  • 在析构函数中捕获异常,然后记录异常抛出事件,最后调用abort.
  • 在析构函数中捕获异常,然后吞掉.
  • 重新设计析构函数,让用户主动去调用可能会跑出异常的成员函数,而不是放在析构函数中调用,在用户没有调用的情况下才在析构函数中调用

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

因为在构造函数和析构函数调用virtual函数的时候,绝对不会下降到derived classes层.例如下面这个例子

class Base
{
public:
    Base()
    {
        string str("derived construct");
        log(str);
    }

    virtual void log(string &str)
    {
        cout << "I am base log:" << str << endl;
    }
};


class Derived : public Base
{
public:

    virtual void log(string &str)
    {
        cout << "I am derived log:" << str << endl;
    }
};

//Derived在构造期间会先对Base进行初始化,在初始化Base类的时候,调用虚函数log的时候调用的是Base类的,而不是Derived的.

对于这个问题的解决方法就是重新设计,避免使用虚函数,通过在derivied中调用base成员函数的时候传递一些信息来实现.

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

为了让赋值操作可以连锁赋值,那么赋值操作符必须返回一个reference指向操作符的左侧实参,例如下面的例子:

class Widget
{
public:
    //.....
    Widget& operator=(const Widget& rhs)
    {
        //....
        return *this;  //返回左侧对象
    }
};

这是一个赋值操作符应该遵循的协议,并无强制性,同时还有+=,-=,*=等操作符都需要满足这个需求.

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

class Bitmap
{
    //....
};

class Widget {
  //....
    Widget& operator=(const Widget& rhs);
private:
    Bitmap *pb;
};

先来看一份不安全的operator=实现版本

Widget& Widget::operator=(const Widget& rhs)
{
    //证同测试: if(this == &rhs) return *this;
    delete pb;  //如果rhs就是this本身,那么这里将pb的内存释放会出现严重问题.这里需要加上证同测试
    pb = new Bitmap(*rhs.pb);
    return *this;
};

尽管加上了证同测试上面的这份代码依然存在很多问题,不具备异常安全性,如果pb = new Bitmap(*rhs.pb);出现了异常,那么Widget最终会持有一个被删除了的指针.因此有了第二版operator=的实现

Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap *pOrig = pb;  //先记住原来的指针,等new成功了,才删除原来的
    pb = new Bitmap(*rhs.pb);
    delete pOrig;
    return *this;
};

通过精心的调整代码的排列顺序现在上面这份代码剧本了异常安全性了,除此之外还有另外一种技术可以实现异常安全性,也就是所谓的copy and swap技术.

Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);
    swap(temp);
    return *this;
};
©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值