读书笔记《Effective C++》条款03:尽可能使用const

来源:互联网 发布:万网域名证书生成 编辑:程序博客网 时间:2024/05/08 20:42

const允许你指定一个语义约束(也就是指定一个“不该被改动”的对象),而编译器会强制实施这项约束。它允许你告诉编译器和其他程序员某值应该保持不变。

可以用const在class外部修饰global或namespace作用域中的常量,或修饰文件、函数、或区块作用域(block scope)中被声明为static的对象。也可以用它修饰class内部的static和non-static成员变量。

面对指针,也可以指出指针自身、指针所指物,或两者都(或都不)是const:

char greeting[] = "Hello";char* p = greeting;//non-const pointer,non-const dataconst char* p = greeting;//non-const pointer,const datachar* const p = greeting;//const pointer,non-const dataconst char* const p = greeting;//const pointer,const data

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

令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。

至于const参数,除非有需要改动参数或local对象,否则请将它们声明为const。

const成员函数

将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。这一类成员函数之所以重要,基于两个理由。第一,它们使class接口比较容易被理解。这是因为,得知哪个函数可以改动对象内容而哪个函数不行,很重要。第二,它们使“操作const对象”成为可能。

两个成员函数如果只是常量性(constness)不同,可以被重载。这是一个重要的C++特性。

class TextBlock {public:TextBlock(std::string text);const char& operator[](std::size_t position) const;//operator[] for const objectchar& operator[](std::size_t position);//operator[] for non-const objectprivate:std::string text;};TextBlock::TextBlock(std::string text){this->text = text;}const char& TextBlock::operator[](std::size_t position) const{std::cout << "const operator[]" << std::endl;return text[position];}char& TextBlock::operator[](std::size_t position){std::cout << "non-const operator[]" << std::endl;return text[position];}int _tmain(int argc, _TCHAR* argv[]){TextBlock tb("Hello");std::cout << tb[0] << std::endl;// call non-const TextBlock::operator[]const TextBlock ctb("World");std::cout << ctb[0] << std::endl;// call const TextBlock:operator[]system("pause");return 0;}
const成员函数不能修改成员变量。例子如下:

class TextBlock {public:TextBlock(std::string text);const char& operator[](std::size_t position) const;//operator[] for const objectchar& operator[](std::size_t position);//operator[] for non-const objectprivate:std::string text;};TextBlock::TextBlock(std::string text){this->text = text;}const char& TextBlock::operator[](std::size_t position) const{std::cout << "const operator[]" << std::endl;text = "abcde";//编译错误:在const成员函数内不能对成员变量text赋值return text[position];}char& TextBlock::operator[](std::size_t position){std::cout << "non-const operator[]" << std::endl;text = "abcde";return text[position];}int _tmain(int argc, _TCHAR* argv[]){TextBlock tb("Hello");std::cout << tb[0] << std::endl;// call non-const TextBlock::operator[]const TextBlock ctb("World");std::cout << ctb[0] << std::endl;// call const TextBlock:operator[]system("pause");return 0;}

要解决这个问题,可以利用C++的一个与const相关的摆动场:mutable(可变的)释放掉non-static成员变量的bitwise constness约束:

class TextBlock {public:TextBlock(std::string text);const char& operator[](std::size_t position) const;//operator[] for const objectchar& operator[](std::size_t position);//operator[] for non-const objectprivate:mutable std::string text;//这些成员变量可能总是会被修改,即使在const成员函数内。};TextBlock::TextBlock(std::string text){this->text = text;}const char& TextBlock::operator[](std::size_t position) const{std::cout << "const operator[]" << std::endl;text = "abcde";//现在就可以在const成员函数内修改成员变量return text[position];}char& TextBlock::operator[](std::size_t position){std::cout << "non-const operator[]" << std::endl;text = "abcde";return text[position];}int _tmain(int argc, _TCHAR* argv[]){TextBlock tb("Hello");std::cout << tb[0] << std::endl;// call non-const TextBlock::operator[]const TextBlock ctb("World");std::cout << ctb[0] << std::endl;// call const TextBlock:operator[]system("pause");return 0;}

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

在上述例子中,假设TextBlock内的operator[]不单只是返回一个reference指向某字符,也执行边界检验、记录访问信息、甚至可能进行数据完善性检验。把所有这些同时放进const和non-const operator[]中,导致大量重复代码。当然,将这些重复代码移到另一个成员函数(往往是个private)并另两个版本的operator[]掉用它,是可能的,但还是重复了一些代码,例如函数调用、两次return语句等等。

真正该做的是实现operator[]的机能一次并使用它两次。也就是说,必须另其中一个调用另一个。这促使我们将常量性转除(casting away constness)。

本例中const operator[]完全做掉了non-const版本该做的一切,唯一的不同是其返回类型多了一个const资格修饰。这种情况下如果将返回值的const转除是安全的,因为不论谁调用non-const operator[]都一定首先有个non-const对象,否则就不能够调用non-const函数。所以另non-const operator[]调用其const兄弟是一个避免代码重复的安全做法——即使过程中需要一个转型动作。下面是代码:

class TextBlock {public:TextBlock(std::string text);const char& operator[](std::size_t position) const;//operator[] for const objectchar& operator[](std::size_t position);//operator[] for non-const objectprivate:std::string text;};TextBlock::TextBlock(std::string text){this->text = text;}const char& TextBlock::operator[](std::size_t position) const{std::cout << "const operator[]" << std::endl;return text[position];}char& TextBlock::operator[](std::size_t position){std::cout << "non-const operator[]" << std::endl;returnconst_cast<char&>(//将operator[]返回值的const转除static_cast<const TextBlock&>(*this)//为*this加上const[position]//调用const operator[]);}

如上述代码,这份代码又两个转型动作,而不是一个。我们打算让non-const operator[]调用其const operator[]兄弟,但non-const operator[]内部若只是单纯调用operator[],会递归调用自己。为了避免无穷递归,我们必须明确指出调用的是const operator[],但C++缺乏直接的语法可以那么做。因此这里将*this从其原始类型TextBook&转型为const TextBlock&。是的,我们使用转型操作为它加上const!所以这里共有两次转型:第一次用来为*this添加const(这使接下来调用operator[]时得以调用const版本),第二次则是从const operator[]的返回值中移除const。

添加const的那一次转型强迫进行了一次安全转型(将non-const对象转为const对象),所以我们使用static_cast。移除const的那个动作只可以藉由const_cast完成。

更值得了解的是,反向做法——令const版本调用non-const版本以避免重复。记住,const成员函数承诺绝不改变其对象的逻辑状态,non-const成员函数却没有这般承诺。如果在const函数内调用non-const函数,就是冒了这样的风险:你曾经承诺不改动的那个对象被改动了。这就是为什么”const成员函数调用non-const成员函数“是一种错误行为:因为对象有可能因此被改动。实际上若要令这样的代码通过编译,你必须使用一个const_cast将*this身上的const性质解放掉。non-const成员函数本来就可以对其对象做任何动作,所以在其中调用一个const成员函数并不会带来风险。这就是为什么本例以static_cast作用于*this的原因:这里并不存在const相关风险。


总结:const使用范围很广,在指针和迭代器身上;在指针、迭代器及reference指涉的对象身上;在函数参数和返回类型身上;在local变量身上;在成员函数身上,林林总总不一而定。尽可能使用它。

要点:

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

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

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


0 0