技术: C++11 nullptr探究

现在说的nullptr不仅仅是规范的问题.

本文主要涉及, nullptr关键字(其实是个对象, 或者常量)以及std::nullptr_t类型, 以及为什么要去使用它.

NULL

NULL是C, C++03或者98中使用最多的,
但是C++和C的定义不一样, 在<cstdlib>中, 其定义如下:

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus

#define NULL 0

#else

#define NULL ((void *)0)

#endif

之所以这样定义的原因,是因为在C语言中,允许 void* 类型隐式转换为任意指针类型,
而C++中不允许这样的强制类型转换,但是可以为任意类型的指针赋0值.
因此,在C++中将NULL 定义为0.

简单说: C++允许你用void*这样的泛指针接收任何类型的指针(一般是安全的, 在分配器里面用的很多), 但是不允许(void*)强制类型转换为具体的指针, 不安全;

所以啊, 只有0代替void*

NULL(预处理)定义为0, 显然更好, 更加直观.

nullptr

nullptr 是 nullptr_t 类型的常量,nullptr来处理转换安全:

  • 该类型定义了转到任意指针类型的转换操作符
  • 不允许该类型的对象转换到非指针类型

写C++程序, 应该用nullptr代替0或者NULL 赋值给指针, C++11标准提倡的.

最简单的例子, 下面有两个重载:

1
2
void f(int);
void f(void*);

那么无论f(0)还是f(NULL)都会调用f(int), 这个时候f(nullptr)才会调用f(void*).

std::nullptr_t

<cstddef>定义了一个std::nullptr_t类型, 其定义如下:

1
typedef decltype(nullptr) nullptr_t;

nullptr准确来说, 是nullptr_t类型的常量, nullptr_t定义如下:
大致代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class nullptr_t
{
public:
template<class T>
inline operator T*() const //定义类型转换操作符,使nullptr_t 可转为任意非类成员指针类型
{ return 0; }


//重载类型转换操作符,使 nullptr_t 可以转换为类 C 中任意的指针类型(数据成员指针/函数成员指针)
//对类中数据成员的指针,T 将被推导为数据成员的类型 eg: int (X::*); 此时 T 为 int,C 为 X
//对类中函数成员的指针,T 将被推导为函数成员的类型 eg: int (X::*)(int); 此时T等效于: typedef int (T)(int)
template<class C, class T>
inline operator T C::*() const
{ return 0; }

private:
void operator&() const;
};

const null_ptr nullptr = {}

模板类型推断

为什么非要使用 nullptr ?

事实上,最迫使你使用nullptr代替0和NULL的原因是,模板类型推断会为0和NULL推断出“错误”的类型当你想要一个空指针时。有了nullptr,模板就不会引起什么问题了。
再想想nullptr不受重载函数选择策略影响,而0和NULL却容易受到影响。所以,当你要说明空指针时,用nullptr,而不是0或者NULL。

请看下面的例子: (lockAndCall是有指针和整数参数重载的函数)

1
2
3
4
5
auto result1 = lockAndCall(f1, f1m, 0);  // 错误
...
auto result2 = lockAndCall(f2, f2m, NULL); // 错误
...
auto result3 = lockAndCall(f3, f3m, nullptr); // 正确

怎么用

具体的使用看你的场景, 最多的应该是函数指针的场景, 那么声明的时候就可以做

1
2
int (A::*pmf)()=nullptr; //指向成员函数的指针  
void (*pmf)()=nullptr; //指向函数的指针

或者检查对象是否为空, 可以这样:

1
2
3
const char *pc=str.c_str(); //data pointers  
if (pc!=nullptr)
cout<<pc<<endl;

总而言之, 原来用NULL的地方, 换成nullptr就行啦.

若我偏不用

实际上, 最好还是使用nullptr, 不用的话, 就我知道的范围, 只要你能避免以下情况即可

避免整数类型与指针类型之间的函数重载
避免使用自动类型推断, 比如auto

文章目录
  1. 1. NULL
  2. 2. nullptr
  3. 3. std::nullptr_t
  4. 4. 模板类型推断
  5. 5. 怎么用
  6. 6. 若我偏不用
|