一、常量处理
开发者们都明白一个道理,能用const限制的访问操作一般尽量限制一下。这样,就会让代码的安全性和稳定性提高一分。但现实世界往往是树欲静而风不止,不是说开发者想怎么样就怎么样。除了有些为了方便自己修改不使用const外,也无法避免别的开发者乱用或不用const。如果说对于新的代码可能还好控制一些,对于很多遗留的代码就不好办了。没人愿意会去读代码然后去加const。这就有可能产生对常量和非常量间的转换,虽然这种转换是一种风险比较大的行为。
另外还有一种情况,C++作为一种“扩展的C”,与C语言打交道的地方非常多,这也会遇到const的处理问题。另外,在C++的重载中,const也是一种形式,这就可能导致一些根本无法预料的问题,一个不小心,就会浪费开发者大量的时间和精力。特别是使用const_cast后,对象的状态在开发者角度发生了改变,如果不加注意,运行的代码路径可能会出现偏差(从常函数转到了普通函数),一些低级问题就涌现了出来。
还有,如果使用const_cast后,分散的常量指针控制,有可能导致不同开发者在维护相同的变量时,产生不正确的数据行为结果。比如一个开发者当常量使用,而另外一个开发者使用const_cast后进行数据的修改。这就会让前者感到很困惑,甚至很难在短时间内定位相关的错误。
种种的上述问题,都表明对常量的处理是一种风险比较大的行为,开发者在应用时,一定要小心应对,不能随意而为之。
二、const_cast分析
const_cast的本质就是告诉编译器对定义的类型限定符(const),要换一个方式来处理其对应的内存。在保证原始数据一致的情况下,只是告诉编译器这块内存已经可以读写。而且需要保证是同一块内存即内存的地址不应发生改变。
说成直白的话,就是常量限定符在被const_cast处理后,已经可以不再受常量的控制了。从多个维度看,它既是常量(原始定义的位置),又不是常量(转换后地址可以被写)。
const_cast存在的目的主要为了处理以下几种状况:
- 单向转换只读
即常量不在原始定义处使用,只转到非常量后读操作 - 处理mutable数据
在特殊情况下,常函数也需要修改数据,那么那些数据可以mutable修饰,以可以被常函数修改 - 减少数据的内存处理
毕竟直接转过去就可以用,不用再重新创建一个新对象,然后再拷贝过去 - 增加适当的灵活性
保证在某些场景下,可以较为灵活的处理业务数据,而不必修改相关的接口 - 兼容老的非常量接口或C接口
在遗留代码或兼容C的接口中,可能存在大量的非const参数应用,而C++中的定义都使用了const,这就需要const_cast
三、应用场景和限制
理解了const_cast的机制,就可以很容易的进行应用,其应用场景主要有:
- 接口处理,特别是兼容C接口
// 参数是非 const 的
void not_const_func(char* s);
void const_cpp_func(const char* s) {
not_const_func(const_cast<char*>(s));
}
- 公用代码,非常函数可以调用常函数
class Demo {
public:
const int *getDataConst() const { return d_; }
int *getData() { return const_cast<int *>(getData1()); }
private:
const int *d_ = new int(33);
};
- 可预见并可控的常量值转非常量值
比如一些测试的特定场景以及其它一些情况,对后果或行为结果不太有意义的情况下,都可以进行。特别是在转成非常量指针后进行写入操作,这个行为可能导致不可预知的后果。
其实在上述的说明中,存在着一些限定的条件,主要有:
- 可以把常量指针通过const_cast转成非常量指针,但直接这种转换会产生不可预测的行为结果。所以尽量不要这样做。特别是在接口的参数处理上,最好采用重载的形式重新定义
- 控制常量指针参数数据的单一性和专有性。不能让一个参数既传入数据也传出数据
- 必须修改的数据请使用mutable
class Demo {
private:
mutable int d_;
public:
int getData() const {
return ++d_;
}
};
- 如果一定要使用const_cast,请严格限定其作用域
四、总结
C++语言之所以被广大的开发者诟病,主要原因就是太灵活,而且关键它对一些灵活而产生的结果并不负责任。也就是常说的“未定义或未知结果”。而这种没有明确定义的结果,就可能让一些开发者产生误会,导致有问题的代码不断的出现而无法及时的进行更正。
学习C++语言,要准确理解库和接口的应用形式,这样才能写出健壮的代码。

4833





