📄 8815.htm
字号:
<p>cs[0] = 'x'; // 错误!——写一个<br> // const string</p>
<p>另外注意,这里的错误只和调用operator[]的返回值有关;operator[]调用本身没问题。 错误产生的原因在于企图对一个const char&赋值,因为被赋值的对象是const版本的operator[]函数的返回值。</p>
<p>还要注意,非const operator[]的返回类型必须是一个char的引用——char本身则不行。如果operator[]真的返回了一个简单的char,如下所示的语句就不会通过编译:</p>
<p>s[0] = 'x';</p>
<p>因为,修改一个“返回值为固定类型”的函数的返回值绝对是不合法的。即使合法,由于c++“通过值(而不是引用)来返回对象”(见条款22)的内部机制的原因,s.data[0]的一个拷贝会被修改,而不是s.data[0]自己,这就不是你所想要的结果了。</p>
<p>让我们停下来看一个基本原理。一个成员函数为const的确切含义是什么?有两种主要的看法:数据意义上的const(bitwise constness)和概念意义上的const(conceptual constness)。</p>
<p>bitwise constness的坚持者认为,当且仅当成员函数不修改对象的任何数据成员(静态数据成员除外)时,即不修改对象中任何一个比特(bit)时,这个成员函数才是const的。bitwise constness最大的好处是可以很容易地检测到违反bitwise constness规定的事件:编译器只用去寻找有无对数据成员的赋值就可以了。实际上,bitwise constness正是c++对const问题的定义,const成员函数不被允许修改它所在对象的任何一个数据成员。</p>
<p>不幸的是,很多不遵守bitwise constness定义的成员函数也可以通过bitwise测试。特别是,一个“修改了指针所指向的数据”的成员函数,其行为显然违反了bitwise constness定义,但如果对象中仅包含这个指针,这个函数也是bitwise const的,编译时会通过。这就和我们的直觉有差异:</p>
<p>class string {<br>public:<br> // 构造函数,使data指向一个<br> // value所指向的数据的拷贝<br> string(const char *value);</p>
<p> ...</p>
<p> operator char *() const { return data;}</p>
<p>private:<br> char *data;<br>};</p>
<p>const string s = "hello"; // 声明常量对象</p>
<p>char *nasty = s; // 调用 operator char*() const</p>
<p>*nasty = 'm'; // 修改s.data[0]</p>
<p>cout << s; // 输出"mello"</p>
<p>显然,在用一个值创建一个常量对象并调用对象的const成员函数时一定有什么错误,对象的值竟然可以修改!(关于这个例子更详细的讨论参见条款29)</p>
<p>这就导致conceptual constness观点的引入。此观点的坚持者认为,一个const成员函数可以修改它所在对象的一些数据(bits) ,但只有在用户不会发觉的情况下。例如,假设string类想保存对象每次被请求时数据的长度:</p>
<p>class string {<br>public:<br> // 构造函数,使data指向一个<br> // value所指向的数据的拷贝<br> string(const char *value): lengthisvalid(false) { ... }</p>
<p> ...</p>
<p> size_t length() const;</p>
<p>private:<br> char *data;</p>
<p> size_t datalength; // 最后计算出的<br> // string的长度</p>
<p> bool lengthisvalid; // 长度当前<br> // 是否合法<br>};</p>
<p>size_t string::length() const<br>{<br> if (!lengthisvalid) {<br> datalength = strlen(data); // 错误!<br> lengthisvalid = true; // 错误!<br> }</p>
<p> return datalength;<br>}</p>
<p>这个length的实现显然不符合“bitwise const”的定义——datalength 和lengthisvalid都可以修改——但对const string对象来说,似乎它一定要是合法的才行。但编译器也不同意, 它们坚持“bitwise constness”,怎么办?</p>
<p>解决方案很简单:利用c++标准组织针对这类情况专门提供的有关const问题的另一个可选方案。此方案使用了关键字mutable,当对非静态数据成员运用mutable时,这些成员的“bitwise constness”限制就被解除:</p>
<p>class string {<br>public:</p>
<p> ... // same as above</p>
<p>private:<br> char *data;</p>
<p> mutable size_t datalength; // 这些数据成员现在<br> // 为mutable;他们可以在<br> mutable bool lengthisvalid; // 任何地方被修改,即使<br> // 在const成员函数里<br>}; </p>
<p>size_t string::length() const<br>{<br> if (!lengthisvalid) {<br> datalength = strlen(data); // 现在合法<br> lengthisvalid = true; // 同样合法<br> }</p>
<p> return datalength;<br>}</p>
<p>mutable在处理“bitwise-constness限制”问题时是一个很好的方案,但它被加入到c++标准中的时间不长,所以有的编译器可能还不支持它。如果是这样,就不得不倒退到c++黑暗的旧时代去,在那儿,生活很简陋,const有时可能会被抛弃。</p>
<p>类c的一个成员函数中,this指针就好象经过如下的声明:</p>
<p>c * const this; // 非const成员函数中</p>
<p>const c * const this; // const成员函数中</p>
<p>这种情况下(即编译器不支持mutable的情况下),如果想使那个有问题的string::length版本对const和非const对象都合法,就只有把this的类型从const c * const改成c * const。不能直接这么做,但可以通过初始化一个局部变量指针,使之指向this所指的同一个对象来间接实现。然后,就可以通过这个局部指针来访问你想修改的成员:</p>
<p>size_t string::length() const<br>{<br> // 定义一个不指向const对象的<br> // 局部版本的this指针<br> string * const localthis =<br> const_cast<string * const>(this);</p>
<p> if (!lengthisvalid) {<br> localthis->datalength = strlen(data);<br> localthis->lengthisvalid = true;<br> }</p>
<p> return datalength;<br>}</p>
<p>做的不是很漂亮。但为了完成想要的功能也就只有这么做。</p>
<p>当然,如果不能保证这个方法一定可行,就不要这么做:比如,一些老的“消除const”的方法就不行。特别是,如果this所指的对象真的是const,即,在定义时被声明为const,那么,“消除const”就会导致不可确定的后果。所以,如果想在成员函数中通过转换消除const,就最好先确信你要转换的对象最初没有被定义为const。</p>
<p>还有一种情况下,通过类型转换消除const会既有用又安全。这就是:将一个const对象传递到一个取非const参数的函数中,同时你又知道参数不会在函数内部被修改的情况时。第二个条件很重要,因为对一个只会被读的对象(不会被写)消除const永远是安全的,即使那个对象最初曾被定义为const。</p>
<p>例如,已经知道有些库不正确地声明了象下面这样的strlen函数:</p>
<p>size_t strlen(char *s);</p>
<p>strlen当然不会去修改s所指的数据——至少我一辈子没看见过。但因为有了这个声明,对一个const char *类型的指针调用这个函数时就会不合法。为解决这个问题,可以在给strlen传参数时安全地把这个指针的const强制转换掉:</p>
<p>const char *klingongreeting = "nuqneh"; // "nuqneh"即"hello"<br> // <br>size_t length =<br> strlen(const_cast<char*>(klingongreeting));</p>
<p>但不要滥用这个方法。只有在被调用的函数(比如本例中的strlen)不会修改它的参数所指的数据时,才能保证它可以正常工作。<br>
</p>
</DIV></div></div>
</center></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -