NULL和nullptr实际问题分析

在C++中推荐使用nullptr代表空指针,虽然我一直坚持这个原则,但是实际开发中没有遇到非nullptr不可的情况,直到写了以下代码(已脱敏):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
template <typename F, typename... Args>
using func_t = typename std::result_of<F(Args...)>::type;

template <typename F, typename... Args,
          typename Rp = func_t<F, Args...>,
          bool is_void_v = std::is_void<Rp>::value>
inline Rp call(F &&func, Args &&...args)
{
    //...
}

问题

这段代码的目的是期望通过call调用一些方法,然后可以对这些方法做一些其他处理。然后假设如此调用:

1
2
3
4
5
6
7
8
typedef void* handle;
int AlgoProcess(handle p)
{
    //processing
    return 0;
}

call(AlgoProcess, NULL);

编译时会报错,提示matching function。可能有如下报错信息:

1
no type named 'type' in 'std::result_of<int (&(long))(void *)>'

关注以上报错信息,可以知道是result_of匹配报错,这里将输入参数NULL解析为了long类型,但是AlgoProcess需求参数是void *类型,所以类型不匹配,又因为没有其他匹配项了,所以编译报错(SFINAE)。

只需要将NULL改成nullptr就可以编译通过,因为nullptr是可以匹配指针的。

1
call(AlgoProcess, nullptr);

那么新问题来了,到底应该不应该为用户匹配一个NULL的版本呢?

因为在大型项目里面无法保证每个用户都会按照你的需要输入nullptr。不过幸好这是编译期的错误,对产品的影响不大。

如果要匹配一个NULL版本应该怎么做?此时需要考虑到和long的输入类型做区分。

nullptr原理

NULL的原理好理解,是一个宏,值是void* (0)或者0。

nullptr的实现原理是什么?超出了我的能力范围,找到过一些用类模拟nullptr的资料,但是都不太符合我的要求,以下文章讲的应该是比较正确的(看不懂…)。总结一句话就是nullptr依赖编译器的词法、语法和语义支持。

nullptr底层实现原理是什么? - 蓝色的回答 - 知乎

结论

小心使用NULL,因为NULL可能会被解析为整形而不是指针类型,因而导致与指针类型不匹配等问题。那么,如果想表示空指针,就坚持使用nullptr吧~(Effective C++中也有此推荐。)