条款03(三):尽可能使用const

条款03:尽可能使用const

Use const whenever possible

在const和non-const成员函数中避免重复

在上一章的介绍中,虽然mutable是一个解决办法,但是它无法解决所有的const问题。例如,在TextBlock中的operator[]不仅仅是返回一个reference以指向某一个字符,它也拥有其他的功能:
执行边界检测、记录访问信息、进行数据完整性的检测等等。
这时,如果要将这些功能同时放到const和non-const的operator[]内,会造成大量的重复的代码

class TextBlock {
public:
...
    //const:
    const char& operator[] (std::size_t position) const 
    { 
        ...//执行边界检测
        ...//记录访问信息
        ...//进行数据完整性的检测
        return text[position]; 
    }

    //non-const:
    char& operator[] (std::size_t position) 
    { 
        ...//执行边界检测
        ...//记录访问信息
        ...//进行数据完整性的检测
        return text[position]; 
        }

private:
    std::string text;
};

当然,上面的代码可以用另外一个private的成员函数来代替并令const和non-const operator[]来进行调用,但是依然重复了一些代码:比如函数的调用、返回语句等等。

此时,正确的做法是实现operator[]的功能一次,并使用它两次。即,必须令其中一个调用另一个。于是,我们所要做的是将常量性移除

在例子中,const operator[]实现了non-const operator[]所要做的事,唯一的区别就是返回类型多了一个const资格修饰。
此时,如果将返回值的const转除(也就是去除其const属性),是安全的,因为无论是谁调用non-const operator[],都一定首先有一个non-const对象,否则就不能调用non-const函数。
因此,令non-从上图 operator[] 调用其const兄弟是一个避免代码重复的安全做法——即使这个过程需要一个转型动作。

class TextBlock {
public:
...
    //const:和原先一样
    const char& operator[] (std::size_t position) const 
    { 
        ...
        ...
        ...
        return text[position]; 
    }

    //non-const:发生区别,直接调用了const op[]
    char& operator[] (std::size_t position)
    {
        return   //直接return
            const_cast<char&>(  //将op[]返回值的const移除
                static_cast<const TextBlock&>(*this)    //为*this加上const
                    [position]    //调用const op[]
                );
            }
        ...
    }

在上面的代码中,发生了两次转型
首先,我们打算让non-const operator[]调用其兄弟const,但是如果直接的去调用operator[],只会递归的调用自己,陷入调用的死循环中。为了避免这种递归的死循环,我们必须要指出调用的是const operator[],但是C++中又没有对应的方法来实现。
因此,这里将*this从其原始类型TextBlock& 转换为const TextBlock&,即使用转型操作来加上const。添加const的这一次转型强迫进行了一次安全转型(将non-const对象转为const对象)。

  • 第一次,用来为*this添加 const,使得接下来调用operator[]是可以条用const的版本,使用static_cast
  • 第二次,则是从const operator[]的返回值中移除const,利用const_cast来完成。

由此,运用const成员函数实现了non-const孪生兄弟得以实现,避免了代码的重复,这种方法是值得学习和理解的。

但是,值得注意的是,反向做法——令const版本调用non-const版本以避免重复——是不应该的!。因为,const成员函数承诺了绝不改变其对象的逻辑状态(logical state),但non-const成员函数却没有这样的承诺。
如果const函数内调用了non-const,就会出现这样的风险:曾经承诺不改动的那个对象被改动了!
而原本的做法——non-const的版本去调用const版本,才是安全的,因为non-const成员函数本身就可以对其对象做任何动作,因此调用const并不会产生风险。

最后:

1、将某些东西声明为const可以帮助编译器侦测出错误的用法。const可以被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。

2、编译器会强制实施bitwise constness,但是你在编写程序是应该使用“概念上的常量性(conceptual constness)”

3、当const和non-const成员函数有实质等价的实现时,令non-const版本去调用const版本可以避免代码重复。

猜你喜欢

转载自blog.csdn.net/lym940928/article/details/80842953