你真的知道常量指针和指针常量吗

C中的由于翻译的问题,有时候我们会混淆常量指针和指针常量的含义,但是我们都知道const char * 和char * const的区别,这里确实需要吐槽一下这两个名字的翻译。

  • 常量指针(const pointer): 指针类型是Type * const,指针变量不可以被修该,但指向的对象可以被修改。
  • 指针常量(pinter to const): 指针类型就是const Type * , 指针变量可以修改,但指向的对象不可以被修改。

是不是觉得上面说的是废话,这个写c的大家都知道呀,那么出一个题目:

1
2
3
4
5
6
7
std::cout<<(typeid(const char *) == typeid(char * const))<<std::endl;
std::cout<<(typeid(const char *) == typeid(char *))<<std::endl;
std::cout<<(typeid(char * const) == typeid(char *))<<std::endl;

std::cout<<std::is_same<const char *, char * const>::value<<std::endl;
std::cout<<std::is_same<const char *, char *>::value<<std::endl;
std::cout<<std::is_same<char * const, char *>::value<<std::endl;

typeid是c++的operator,用于返回表达式类型的相关信息,是c++提供的RTTI(run-time type indentification)运行时类型识别功能;
std::is_same<>是C++11的type_traits组件提供的判断两个数据类型是否相同的模版类;

你能说出输出结果吗?我敢肯定没几个人能答对的,结果是:”0 0 1 0 0 0”; 我们知道const char * , char * const, char * , 是三种不同的类型,为什么第三个输出是1呢,
这里的原因是:

Quote[link]: “In all cases, cv-qualifiers are ignored by typeid (that is, typeid(T) == typeid(const T))”

cv-qualifiers是指const和volatile限定符,那为什么const char * 的const修饰符没有被忽略呢, 这里其实就是本文要重点说明的:const char * 中的const不是修饰该指针类型的,而是修饰指针所指向的数据类型是const char类型, 所以说cv-qualifiers中的限定符是指传入类型的限定符,而不是所指类型的限定符,所以typeid(char * const) == typeid(char *), 而typeid(const char *) != typeid(char *);

问题的引入

你会在C++标准中看到很多cv-qualifiers的描述,都是上述的含义,所以这里引入之所以会写这个文章的起因:判断两个指针对应的原始type是不是相同,下面是我一开始写的第一版判断(这里简化了一下):

1
std::cout<<std::is_same<char, std::remove_pointer<std::remove_const<const char* >::type>::type>::value<<std::endl;

C++11的type_triats组件的remove_pointer和remove_const

先看看c++11的type_triats组件提供的remove_ponter和remove_const的功能和可能实现:
remove_pinter用于移除指针类型,获得其所指向数据的类型,它的Possible implementation:

1
2
3
4
5
template< class T > struct remove_pointer                    {typedef T type;};
template< class T > struct remove_pointer<T*> {typedef T type;};
template< class T > struct remove_pointer<T* const> {typedef T type;};
template< class T > struct remove_pointer<T* volatile> {typedef T type;};
template< class T > struct remove_pointer<T* const volatile> {typedef T type;};

所以会有如下:

1
2
remove_pointer<const char *>::type == const char
remove_pointer<char * const>::type == char

remove_const用于移除类型的const的限定,它的Possible implementation:

1
2
3
4
5
6
7
8
9
10
template< class T >
struct remove_cv {
typedef typename std::remove_volatile<typename std::remove_const<T>::type>::type type;
};

template< class T > struct remove_const { typedef T type; };
template< class T > struct remove_const<const T> { typedef T type; };

template< class T > struct remove_volatile { typedef T type; };
template< class T > struct remove_volatile<volatile T> { typedef T type; };

Quote[link]: “Removing const/volatile from const volatile int * does not modify the type, because the pointer itself is neither const nor volatile.”

所以会有如下:

1
2
remove_const<const char *>::type == const char*
remove_const<char * const>::type == char *

问题的解决

那么回到上面的问题中去,第一版输出的结果是0,原因是c++的type_traits组件的remove_const也是指cv-qualifiers,所以remove_const<const char * >后的结果还是const char *, 因为const限定符不是修饰指针的,而是修饰所指向的对象,所以正确的做法应该如下:

1
std::cout<<std::is_same<char, std::remove_const<std::remove_pointer<const char* >::type>::type>::value<<std::endl;

首先通过remove_pointer将const char * 类型变成const char, 然后就可以通过remove_const来是const char 变成char。有人在stackoverflow上也提过这个问题:How to remove the const from ‘char const*‘

最后

C++11的type_triats组件还是功能很强大的,它提供了一系列的类用于在编译期间获取类型信息,强烈推荐学习,它和C++很早提供的typeinfo组件,用于在RTTI运行时识别相比,更加实用,方便。

最后总结一下

1
2
3
const char * p1;
char * const p2;
char * p3;

p1, p2, p3是三种不同的类型变量:

  • p1的const限定符是修饰其所指向的对象,即p1指向的对象为const char类型, 所以remove_pointer结果为const char,remove_const结果还是const char *;
  • p2的const限定符是修饰p2自己的,p2指向对象为char类型, 所以remove_pointer结果为char,remove_const结果还是char *;

参考

1. C++11的type_traits组件
2. How to remove the const from ‘char const*‘
3. typeid operator