Effective C++条款03:尽可能使用const(Use const whenever possible)
创始人
2024-01-29 17:35:11
0

Effective C++条款03:尽可能使用const(Use const whenever possible)

  • 条款03:尽可能使用const
    • 1、const 与 STL迭代器
    • 2、const 在函数中的应用
    • 3、const 成员函数
    • 4、bitwise constness与logical constness阵营
    • 5、mutable关键字
    • 6、在 const 和 non-const 成员函数中避免重复
    • 7、小结
  • 总结


《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:


条款03:尽可能使用const

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

1、const 与 STL迭代器

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

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

同理:

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

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

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

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一方的。

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

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(                 //将operator[]返回值中的const移除static_cast(*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成员函数的。

7、小结

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

总结

期待大家和我交流,留言或者私信,一起学习,一起进步!

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...