归去来兮

 

Effective C++读书笔记之二 :尽可能使用const

条款三:尽可能使用const(use const whenever possible)
const允许你指定一个语义约束,而编译器会强制实施这项约束。它允许你告诉编译器和其他程序员某值应该保持不变。有一条约束需要注意,那就是:如果const出现在*号的左边,那就是说被指物是常量;如果出现在星号右边,则表示指针本身是常量;如果出现在两边,则被指物和指针都是常量。如果被指物是常量,则关键字const写在类型的前面和类型之后,星号之前两种所表示的语义是相同的。例如下面这两种写法是一样的:
void f1(const Widget* pw);
void f2(Widget const * pw);
const也可用来修饰STL中的迭代器。声明迭代器为const就想声明指针为const一样(即T* const 指针),表示这个迭代器不得指向不同的东西。但它所指的东西的值是可以改变的。如果希望迭代器所指的东西不可改变(即模拟一个const T*指针),需要的是const_iterator:
std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin();// same as T* const
*iter = 10;                                                        //no problem
++iter;                                                              //wrong!!
std::vector<int>::const_iterator cIter = vec.begin();//same as const T*
*iter = 10;                                                             //wrong!!
++iter;                                                                  //no problem

const 最具威力(?)的用法是面对函数声明时的应用。在一个函数声明式内,const可以和函数返回值,各参数,函数自身(成员函数)产生关联。
令函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。例如,考虑有理数的operator*声明式:
class Rational(){...};
const Rational operator* (const Rational & lhs, const Rational & rhs);
也许你会说为什么返回一个const对象?原因是如果不这样别人可能实现这样的暴行:
Rational a,b,c;
...
(a*b)=c;
下面,主要说明const作用于成员函数。
许多人都忽视了这么一个事实,那就是如果两个成员函数只是常量性不同,那么他们是可以重载的。考虑以下这个用来表示一大块文字的class:

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;
}
;
TextBlock的operator[]可以这么使用:
TextBlock tb(
"Hello");
std::cout 
<< tb[0];  //调用non-const 

const TextBlock ctb("Hello");
std::cont 
<< ctb[0]; //调用const

真是情形中const对象多用于passed by pointer-to-const或passed by reference-to-const的传递结果。上述的ctb太过于造作,下边这个比较真实:
void print (const TextBlocd& ctb)
{
  std::cout 
<< ctb[0];
  
}

只用重载operator[]并对不同的版本给予不同的返回类型,就可以令const和non-const获得不同的处理。
此处需要注意一点,non-const operator[]的返回类型是个reference to char,不是char。如果operator[]返回的是个char,下边的赋值就不能通过编译:
tb[0] = 'x'; //error c2106: ' = ' : left operand must be l-value
那是因为,如果函数的返回类型是个内置类型,那么改动函数的返回值从来就不合法。纵使合法,C++以by value返回对象这一事实(条款20)意味着改动的其实只是tb.text[0]的一个副本,不是tb.text[0]本身,那不是我们想要的结果。
下边来说说在const和non-const成员函数中避免重复
假设TextBlock(和CTextBlock)内的operator[]不单只是返回一个reference指向某字符,也执行边界检查、志记访问信息、甚至可能进行数据完整性检验。把所有这些同时放进const和non-const operator[]中,导致这样的一个怪物:
class TextBlock{
public:

const char& operator[](std::size_t position) const
{
      
//边界检查(bounds checking)
      //志记数据访问(log access data)
      //检验数据完整性(verify data integrity)
return text[position];
}

char& operator[](std::size_t position)
{
      
//边界检查(bounds checking)
      //志记数据访问(log access data)
      //检验数据完整性(verify data integrity)
return text[position];
}

private:
std::
string text;
}
;
其中代码的代码重复性及伴随的编译时间,维护,代码膨胀等问题真是令人头疼啊。当然了,将边界检查……等所有代码移植到另一个成员函数,并令两个版本的operator[]调用它,是可能的,但是还是重复了一些代码,例如函数调用,两次return语句等。
我们真正要做的,是实现operator[]的机能一次并使用它两次。也就是说,你必须使一个调用另一个。这促使我们将常量性转除(casting away constness)。
就一般而言,casting是一个糟糕的想法,在条款27中有详细的说明。然而代码重复也不是什么令人愉快的经验。本例中cosnt operator[]完全做掉了non-const版本该做的一切,唯一不同是其返回类型多了一个const资格修饰。这种情况下如果将返回值的const转除是安全的,因为不论谁调用non-const operator[]都一定首先有个non-const对象,否则就不能够调用non-const函数。所以令non-const operator[]调用其const兄弟是一个避免重复的安全做法:
class TextBlock{
public:

const char& operator[](std::size_t position) const
{



return text[position];
}

char& operator[](std::size_t position)
{
const_cast
<char&>(static_cast<const TextBlock&>
(
*this)[position]);
}


}
;
这里面有两个转型动作,而不是一个。我们打算让non-const operator[]调用const兄弟,但是non-const如果只是单纯调用operator[],会递归调用自己。为了避免无穷递归,我们必须明确指出调用的是const operator[]。因此,这里将*this从其原始类型TextBlock&转型为const TextBlock&。所以这里有两次转型:第一次用来为*this添加const,第二次则是从const operator[]的返回值中移除const。添加const的那一次转型强迫进行了一次安全转型,所以采用static_cast。移除const的那个动作只能由const_cast完成,没有其他选择。
下面来考虑一下反向的做法:令const来调用non-const以避免重复。这个不是我们应该做的。const成员函数承诺绝对不改变其对象的逻辑状态,non-const成员函数却没有这般承诺。如果在const函数内部调用了non-const函数,就是冒了这样的风险:你曾经承诺不改动的那个对象被改动了。这就是为什么“const成员函数调用non-const成员函数”是一种错误行为:因为对象有可能因此而被改动。反向调用才是安全的:non-const函数本来就可以对其对象做任何动作,所以在其中调用一个const成员函数并不会带来任何风险。

本条目总结:

Things to Remember

  • Declaring something const helps compilers detect usage errors. const can be applied to objects at any scope, to function parameters and return types, and to member functions as a whole.

  • Compilers enforce bitwise constness, but you should program using conceptual constness.

  • When const and non-const member functions have essentially identical implementations, code duplication can be avoided by having the non-const version call the const version.

posted on 2008-12-09 23:00 Edmund 阅读(228) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


导航

统计

常用链接

留言簿(1)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜