Effective C++条款03:让自己习惯C++之(尽可能使用const)

一、指针常量、常量指针、常量指针常量

二、const应用之STL迭代器

  • STL的迭代器就是一个很好介绍const的例子
  • ①如果想希望可以通过迭代器修改值,并且改动迭代器的位置,那么可以使用iterator类型的迭代器
  • ②如果我们希望可以通过迭代器修改值,但是希望迭代器指针不能进行移动。那么可以在"iterator"迭代器类型的前面加上一个const
  • ③如果我们希望通过迭代器获取值但是不能改变值,并且迭代器指针可以进行移动,那么可以使用“const_iterator”类型的迭代器
  • ④如果我们既不希望通过迭代器改变值,迭代器指针也不能进行移动,那么可以在“const_iterator”前面加上const
std::vector<int> vec;

//iter1类型为int*
std::vector<int>::iterator iter1 = vec.begin();             
*iter1 = 1; //正确
iter1++;    //正确

//iter2类型为int* const
const std::vector<int>::iterator iter2 = vec.begin();       
*iter2 = 1; //正确
iter2++;    //错误

//iter3类型为const int*
std::vector<int>::const_iterator iter3 = vec.cbegin();      
*iter3 = 1; //错误
iter3++;    //正确

//iter4类型为const int* const
const std::vector<int>::const_iterator iter4 = vec.cbegin();
*iter4 = 1; //错误
iter4++;    //错误

三、const在函数返回值、参数中的应用

在返回值的应用

  • 令函数返回一个常量值,往往可以降低因客户错误而造成的意外
  • 举个例子,下面是一个有理数的operator*声明式:
class Rational{};

const Rational operator*(const Rational& lhs, const Rational& rhs);
  • 返回一个const类型可以防止以下的错误情况发生,一个有理数的乘积是不可能改变的,因此赋值操作也没有意义,所以我们将operator*的返回值设置为const:
Rational a,b,c;

//do something

(a*b)=c;//将a*b的结果重新赋值了一个值
  • 在参数中的应用:同理,在上面的operator*中,我们将两个有理数进行相乘,但是乘数本身不会改变,因此将参数设置为const

四、const成员函数

  • 使用const成员函数的两个理由:
    • ①它们使class接口比较容易理解,因为可以得知哪个函数可以改动对象而哪个函数不行
    • ②它们可以“操作const对象”。这对编写高效代码是个关键(见条款20,改善C++程序效率的一个根本办法是以const引用方式传递对象,而这个技术的前提是,我们有const成员函数可用来处理const对象)

const成员函数可以被重载

  • const成员函数与非const成员函数可以构成重载
  • 例如下面的class被设计用来表现一大块文字:
class textBlock
{
public:
    char& operator[](std::size_t position){
        return text[position];
    }
    const char& operator[](std::size_t position)const {
        return text[position];
    }
private:
    std::string text;
};
  • 那么根据对象类型的不同,对调用不同的operator[]函数
textBlock tb("Hello");
std::cout << tb[0];  //调用非const版本的operator[]
tb[0] = 'x';  //正确
	
const textBlock ctb("World");
std::cout << ctb[0];  //调用const版本的operator[]
ctb[0] = 'y';  //错误
  • const对象大多被用于常量指针形式或常量引用的形式传递给函数,因此也可以作为一个函数传参的例子:
void print(const textBlock& ctb)
{
	std::cout << ctb[0];  //调用const版本的operator[]
}

五、bitwise constness、logical constness

  • const成员函数的使用由两种流行的概念:bitwise constness(又称physical constness)和logical constness

bitwise constness(位常量)

  • bitwise constness阵营的人认为,成员函数只有在不更改对象任何成员变量(static除外)时才可以说是const
class CTextBlock
{
public:
    //没有改变成员变量的值
    char& operator[](std::size_t position)const {
        return pText[position];
    }
private:
    char *pText;
};

logical constness(逻辑常量)

  • logical constness阵营的人认为,const虽然不可以在const函数中改变成员变量(static除外)的值,但是允许在某些不知情的情况下改变了类中成员变量的值
  • 例如上面CTextBlock类的的operator[]函数内部虽然没有改变成员变量的值,但是可以通过其返回值进行更改
CTextBlock cctb("Hello");

char *p= cctb[0]; //取得其返回值
p = 'J'; //改变了

六、mutable关键字

  • 上面bitwise constness思想导致不能在const成员函数内更改任何成员变量(static除外)的值。但是有些成员变量时可以改变的,那么可以使用mutable关键字
  • 例如下面的length const函数,我们可以在其中更改两个成员变量的值
class CTextBlock
{
public:
    std::size_t length()const;
private:
    char *pText;
    mutable std::size_t textLength;
    mutable bool lengthIsValid;
};

std::size_t CTextBlock::length()const
{
    if (!lengthIsValid) {
        textLength = std::strlen(pText);
        lengthIsValid = true;
    }

    return textLength;
}

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

先来看看代码重复的class样例

  • 改变上面的TextBlock类,实际代码中operator[]中不仅仅返回一个字符的引用,还可能在之前执行边界检测、志记数据访问、检验数据完整性等,因此代码量会增加
  • 可以看到这样的代码比较臃肿,当然你也可以把执行边界检测、志记数据访问、检验数据完整性等操作封装于一个private成员函数中,然后使用operator[]调用这个函数,但是代码还是重复了
class TextBlock
{
public:
	const char& operator[](std::size_t position)const
	{
		//...边界检测
		//...志记数据访问
		//...检验数据完整性
		return text[position];
	}
	char& operator[](std::size_t position)
	{
		//...边界检测
		//...志记数据访问
		//...检验数据完整性
		return text[position];
	}
private:
	std::string text;
};
  • 现在要做的就是:将两种类型的operator[]封装为一种。也就是说,你必须令其中一个调用另一个。这促使我们将常量性移除

代码转型缩减

  • 下面我们将non-const成员函数中的代码更改了
class TextBlock
{
public:
    const char& operator[](std::size_t position)const
    {
        //...边界检测
        //...志记数据访问
        //...检验数据完整性
        return text[position];
    }
    char& operator[](std::size_t position)
    {
        return const_cast<char&>(                 //将operator[]返回值中的const移除
            static_cast<const TextBlock&>(*this)  //为*this加上const
                [position]                        //调用const operator[]
        );
    }
private:
    std::string text;
};
  • 两次类型转换:
    • 第一次:static_cast,我们在non-const operator[]中调用const operator[],但是non-const operator[]若只是单纯调用operator[],会递归调用自己(无限循环),因此,我们需要明确指出调用的是const operator[],这里使用static_cast将*this将TextBlock&换换位const TextBlock&类型
    • 第二次:const_cast,从const operator[]的返回值中移除const
  • 虽然这样的语法比较奇特,不容易理解,但是对于“避免代码重复”来说,这样的损失是值得的
  • 为什么不在const成员函数中调用non-const版本:因为编译器不允许,在const成员函数中是不允许调用non-const成员函数的

八、总结

  • ①将某些东西声明为const课帮助编译器侦测处错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体
  • ②编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”
  • ③当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复
发布了1399 篇原创文章 · 获赞 958 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104148149
今日推荐