相关文章推荐
心软的大脸猫  ·  android ...·  3 周前    · 
星星上的大熊猫  ·  GetSystemTime 函数 ...·  1 年前    · 
热心的皮蛋  ·  React Hooks 里的 ...·  1 年前    · 
《Effective C++》条款03:尽可能使用const

《Effective C++》条款03:尽可能使用const

条款03:尽可能使用const

const 语法虽然变化很多,但并不高深莫测,如果关键字 const 出现在星号左边,表示被指向的变量为常量,如果出现在星号右边,则表示指针本身是常量,因为指针也是变量嘛。关于 const 关键字的更多的知识可以参考 C++ const关键字 ,这篇文章。

3.1 const 与 STL 迭代器

STL 迭代器系以指针为根据塑模出来,所以迭代器的作用就像个T*指针。声明迭代器为 const 就像声明指针为 const 一样。

  • 如果你希望通过迭代器获取值但是不能改变值,并且迭代器指针可以进行移动,你需要的是“const_iterator”类型的迭代器
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin(); // iter就像个T* const           
*iter = 10; //正确,改变iter所指物
++iter;    //错误,iter是const

同理:

  • 如果你希望可以通过迭代器修改值,并且改动迭代器的位置,那么可以使用iterator类型的迭代器
  • 如果你希望可以通过迭代器修改值,但是希望迭代器指针不能进行移动。那么可以在"iterator"迭代器类型的前面加上一个const
  • 如果你既不希望通过迭代器改变值,迭代器指针也不能进行移动,那么可以在“const_iterator”前面加上const

现在,你再回头看,就能够发现,咿,没错,就和const+指针用法是一样的。

3.2 const 在函数中的应用

const最具威力的用法是面对函数声明时的应用。在一个函数声明式内,const可以和函数返回值,各参数、函数自身(如果是成员函数)相关联。

  • 在函数返回值中的应用

令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。 举个例子,下面是一个有理数( 详情见条款24 )的operator*声明式:

class Rational{...};
const Rational operator*(const Rational& lhs, const Rational& rhs);

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

Rational a,b,c;
(a*b)=c;//将a*b的结果重新赋值了一个值
  • 在函数参数中的应用

对于 const 参数,没有特别新颖的概念,你应该在必要使用它们的时候使用它们,除非你需要改动参数。

3.3 const 成员函数

将const实施在成员函数的目的,是为了确认该成员函数可作用于 const 对象身上。这一类成员函数之所以重,基于两个理由:

  • ①它们使class接口比较容易理解,因为可以得知哪个函数可以改动对象而哪个函数不行,很重要。
  • ②它们使**“操作const对象” 成为可能。这对编写高效代码是个关键(见 条款20 ,改善C++程序效率的一个根本办法是以 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[] 并对不同版本的返回类型就可以令 const 和 non-const 获取不同处理:

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[]
}

3.4 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'; //改变了

到这里,你就可以对bitwise constness与logical constness的观点了解的很清晰了,前者就是站在不更改对象内的任何一个bit一方的,这种论点只需编译器寻找成员变量的赋值动作就能被反驳,后者则是站在能够修改对象内的某些bits一方的。

3.5 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;
}

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

对于"bitwise constness"非我所欲的问题,mutable是个解决办法,但它并不能解决所有 const 难题。举个例子:

改变上面的TextBlock类,实际代码中operator[]中不仅仅返回一个字符的引用,还可能在之前执行边界检测、志记数据访问、检验数据完整性等,因此代码量会增加。

可以看到这样的代码比较臃肿,当然你也可以把执行边界检测、志记数据访问、检验数据完整性等操作封装于一个private成员函数中,然后使用operator[]调用这个函数,但你依旧重复了一些代码,如函数调用,两次 return 语句等。

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[]的机能一次并使用它两次(将两种类型的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