考虑如下代码:
1 void g(int* i){ } 2 3 int main(){ 4 5 g(0); //ok 调用g((int*)0) 6 7 const int x = 0; 8 g(x); //ok 调用g((int*)0) 9 10 int x=0; 11 g(x); //error. 编译错误,int无法转换int* 12 13 return 0; 14 15 }
文字量0的类型,永远是int
编译期int常量0,在遇到转换成指针的场合,会被隐式转换成指针。其它常量不会,1,2,3...都没有这个待遇
0值得这个特殊待遇,遇到变量就会不好使了。例如:第11行,变量x,即使其值为0也没用。这个规则会延续到模板,例如:
template<class PointerType> void template_call( PointerType x ){ g(x); } void g(int* i){} int main(){ template_call(0); }
这相当于,调用的是:
void template_call( int x ) { g(x); //11行一样的失败 }
第一个解决方法是:手动写 template_call( (int*)0); 也没什么缺点,就是需要程序员去查勘g(int*)的参数类型,然后回来把int*拷贝到0的前面。实际上指向任意类型的万能空指针破产了,这是优秀(懒惰)的程序员不能容忍的。因此第二个方案nullptr引入了。
namespace study { const class nullptr_t { public: template<class T> inline operator T*() const //隐式转化 { return 0; } template<class C, class T> //隐式转化 inline operator T C::*() const { return 0; } void operator&() const = delete; } ; } template<class PointerType> void template_call( PointerType a ){ g(a); } void g(int* i){ } int main(){ //代替:template_call((int*)0) template_call( study::nullptr_t{} ); }
template_call( study::nullptr_t{}) 实例化模板参数PointerType=study::nullptr_t
这样g( study::nullptr_t{} );匹配不了g(int*)啊!!但是我们看到有个nullptr_t到int*的隐式转换:
const class nullptr_t { public: inline operator int*() const //隐式转化 { return 0; }
这样g( study::nullptr_t{}.operator int*() ); 就是 int* tmp=0; g(tmp);的展开结果了。万能空指针又回来了。
f( study::nullptr_t{}); 在标准库std名字空间里是f( std::nullptr_t{} ); 但是还是没有0简洁啊!!
常规思路可能定义个全局对象,例如constexpr std::nullptr_t nullptr; 然后,就简化成f( std::nullptr ); 但是前面那个std::五个字符还是太复杂!!
nullptr就不再是个全局对象了,而被收录成关键字!!就像for ,class一样的。这下好了,std::也省了。就成了f(nullptr)
f( nullptr ) 比 f(0)多敲打6个字符,但是比起f(NULL)只多敲打了3个字符,好像还说得过去。如果把nullptr改成np为关键字,则f(np)是不是更省键盘呢?