const_cast是一个运算符,和dynamic_cast、static_cast、reinterpret_cast一样都是用于类型的转换的,本来想将这四个一起写的,但是查了const_cast相关的资料,发现const_cast可以牵扯到的也不少,所以单独用一篇博客说明了。
1. 用法
const_cast的作用很简单,就是去除或增加类型中的const属性或volatile属性,注意这里也可以用于增加这两种属性。其原型如下:
const_cast<type_id> (expression)
虽然这里用了“去除”,但实际上并没有改变原类型的const属性或volatile属性,而是提供了一个接口(指针或引用),使得我们可以通过这个新的接口(变量)来改变原类型的值。在const_cast中,type_id也只能是指针或者引用,因为只有通过指针或引用才能得到原类型的地址,从而对其内存内容进行修改。
个人觉得const_cast就有点类似于显式类型转换,将原来变量起了新的别名或者将其地址赋给新另一个指针,只不过它是限定源类型和目的类型是要一样的。更详细的介绍可以看这里:const_cast 转换。
下面是使用的例子:
#include <iostream>
using namespace std;
int main()
{
const int a = 10;
// 强制转换
int *p = (int *)(&a);
*p = 20;
cout << "a = " << a << ", *p = " << *p << endl;
// const_cast到指针
int *p2 = const_cast<int *>(&a);
*p2 = 30;
cout << "a = " << a << ", *p = " << *p << endl;
// const_cast到引用
int &p3 = const_cast<int &>(a);
p3 = 40;
cout << "a = " << a << ", *p = " << *p << endl;
return 0;
}
上面举出了三种转换的方法,第一种是将取地址后保存,后面两种则分别用了const_cast的指针和引用方式,这三种方式都可以修改const变量a对应内存的内容。但是上面程序的输出却可能与我们想象中的不一样:
a = 10, *p = 20
a = 10, *p = 30
a = 10, *p = 40
可以看到,即使设置“*p = 20”、“*p2 = 30”、“p3 = 40”,在三次输出中,a的值都是最初定义的值10,并没有改变,这是为什么呢?原因就在于接下来要介绍的概念:常量折叠。
2. 常量折叠(const folding)
常量折叠与编译器的工作原理有关,是编译器的一种编译优化。在编译器进行语法分析的时候,将常量表达式计算求职,并用求得的值来替换表达式,放入常量表。所以在上面的例子中,编译器在优化的过程中,会把碰到的a(为const常量)全部以内容10替换掉,跟宏的替换有点类似。
与常量折叠紧关的概念是复写传播(copy propagation),这里就不讲了。
虽然在运行阶段a的内容确实是被改变了,但它在打印的部分已被替换为10了,导致打印出来的值与我们期望的就不一样了。这让我想到在收集资料时看到的这句话:
const是一个编译时约定,而不是运行时约定。
另外,常量折叠只对原生类型其作用,对我们自定义的类型,是不会起作用的。下面是我测试的例子:
#include <iostream>
#include <stdio.h>
using namespace std;
typedef struct _Test {
int a;
_Test() {
a = 10;
};
} Test;
int main()
{
const Test b;
Test *b1 = const_cast<Test *>(&b);
cout << "b = " << b.a << endl;
b1->a = 100;
cout << "b = " << b.a << ", *b1 = " << b1->a << endl;
return 0;
}
输出内容如下:
b = 10
b = 100, *b1 = 100
可以看到对于自定义的类型,是不存在常量折叠的想象的,估计是对于自定义类型并不会进行相应的优化操作吧。
3. const_cast的使用场景
正常情况下,当我们在创建变量时加上const关键字则说明我们是不希望他被改变的,不加上const则表明我们存在改变它的可能性。那const_cast究竟在什么情况下会用到呢?因为正常情况下我们写代码函数什么的肯定是对变量的修改情况有了解的。
经过搜索,发现const_cast似乎是应该用在这种情况的:假如函数A中调用B函数,B中定义参数是const的而A传入的变量是非const,这个时候我们就可以用const_cast给传入的变量加上const属性了。反过来B中定义参数是非const而A传入的变量是const的,则我们可以用const_cast将传入变量的const属性去掉了(当然这种情况下就算不加const属性也是可以的)。
#include <iostream>
using namespace std;
void not_const_char(char *str)
{
cout << str + 5 << endl;
}
void const_char(const char *str)
{
cout << str + 5 << endl;
}
int main()
{
const char str[] = "abcdefgggg";
char str1[] = "efggggeeee";
not_const_char(str1); // ok
// not_const_char(str); // compile fail
not_const_char(const_cast<char *>(str)); // ok
const_char(str1); // ok
const_char(str); // ok
return 0;
}
可以看到注释掉的调用“not_const_char(str)”是编译失败的,也就是说如果将const属性的变量传入到非const的函数中,编译不会通过(const是编译时约定,它预期在传入的函数中会对其内容修改),但用const_cast对传入的变量去掉const属性后再传入,就能正常编译通过了。
搜索了下公司的项目代码,简略看到有两种情况用到了const_cast。
第一种是在单例模式中的函数修改类中的静态成员对象时:
const_cast<SomethingStatistic&>(statistic_).inc_recv_num();
第二种如下:
void App::Fish(uint64_t id, const char* data, uint32_t len) {
...
reinterpret_cast<CHeader*>(const_cast<char*>(data) +
sizeof(FHead) - sizeof(CHeader));
...
}
第一种我也不太理解,但看到在protobuf的源码中也有类似的用法,希望有懂的人可以说明下。第二种就有点类似于我上面所举的例子,只是这里需要对data数据转换成需要的类型:CHeader是非const,而传入的data是const的,所以在需要去掉data的const属性吧。
以上是全部内容,学无止境,说得不对的请指正,有什么更深的了解也希望大家能在评论里说出来。