Item 8: Prefer nullptr to 0 and NULL.

Item 8: Prefer nullptr to 0 and NULL.


这次是对 Effective Modern C++ Item 8 的学习笔记。

C++ 中,字面值 0 是一个 int 类型,不是一个指针类型,虽然 C++ 根据上下文可以将字面值 0 解释成一个空指针,但本质上,请注意字面值 0 是一个 int 类型。

实际上,NULL 的情况也一样,它也不是一个指针类型,根据实现情况来定,可以被允许实现为 long 类型,但本质上也不是一个指针类型。

字面值 0 和 NULL 不是指针类型这一事实会导致了一些让人困惑的场景,比如在 C++98 下:

void f(int);    // three overloads of f
void f(bool);
void f(void*); 
f(0);           // calls f(int), not f(void*)
f(NULL);        // might not compile, but typically calls f(int). Never calls f(void*)

f(0) 会匹配到第一个,不会匹配 f(void*)NULL 的实现是 0L,可以转换成 int,boolvoid* ,这会导致匹配失败,除非有一个 f(long) 的重载。对于程序员来说,f(NULL) 预想的是调用 f(void*),但 C++ 实际上却去匹配参数为整型的 f,这是违反直觉的。因此, 对于 C++98 的程序员,最好避免使用指针类型重载整型。到了 C++11,上面的这个建议依然有效,因为开发者很有可能继续使用 0NULL 作为空指针。

C++11 后,建议大家使用 nullptr 作为空指针。nullptr 不是一个整型,它也不是一个确定的指针类型,可以把它理解为任意类型的指针,它的准确类型是 std::nullptr_t,可以隐式转换为任意指针类型的类型。用 nullptr 代替 0 和 NULL,可以使得重载函数的调用非常明确。

f(nullptr);  // calls f(void*) overload

使用 nullptr 的另一个优势是可以提高代码的清晰度,尤其是使用 auto 变量时:

auto result = findRecord( /* arguments */ );
  if (result == 0) {
  ...
}

这里,如果你不清楚 findRecord 返回值的类型时,你可能就不清楚 result 是一个指针类型还是一个整型。但如果使用 nullptr 代替 0,代码将更加清晰,result 一定是一个指针类型,就不会模棱两可了:

auto result = findRecord( /* arguments */ );
  if (result == nullptr) {
  ...
}

在我们使用模板时候,nullptr 的优势将更加引人注目。假设你有这样的一些函数,只有当对应的互斥量被锁定的时候,这些函数才可以被调用,每个函数的参数是不同类型的指针:

int f1(std::shared_ptr<Widget> spw);    // call these only when the appropriate mutex is locked
double f2(std::unique_ptr<Widget> upw); 
bool f3(Widget* pw);

传空指针调用这些函数可能是这样的:

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3

using MuxGuard = std::lock_guard<std::mutex>;  // C++11 typedef; see Item 9
...

{
  MuxGuard g(f1m);      // lock mutex for f1 
  auto result = f1(0);  // pass 0 as null ptr to f1 unlock mutex
}
...

{
  MuxGuard g(f2m);         // lock mutex for f2 
  auto result = f2(NULL);  // pass NULL as null ptr to f2 unlock mutex
}
...

{
  MuxGuard g(f3m);            // lock mutex for f3 
  auto result = f1(nullptr);  // pass nullptr as null ptr to f3 unlock mutex
}

虽然前面两个调用没有使用 nullptr,但是还可以工作。但是上面代码最大的问题是调用代码没有复用,引入模板来解决这个问题:

template<typename FuncType,
		 typename MuxType,
		 typename PtrType>
auto lockAndCall(FuncType func,
				 MuxType& mutex,
				 PtrType ptr) -> decltype(func(ptr))
{
  MuxGuard g(mutex);
  return func(ptr);
}

C++ 14 中可以使用 decltype(auto) 代替上面的 —> :

template<typename FuncType,
		 typename MuxType,
		 typename PtrType>
decltype(auto) lockAndCall(FuncType func,      // C++14
						   MuxType& mutex,
						   PtrType ptr)
{
  MuxGuard g(mutex);
  return func(ptr);
}

对于上面的两个版本,调用代码可以如下:

auto result1 = lockAndCall(f1, f1m, 0);        // error!
...
auto result2 = lockAndCall(f2, f2m, NULL);     // error!
...
auto result3 = lockAndCall(f3, f3m, nullptr);  // fine

前面的两种调用将会失败。在第一个调用中,将 0 传入 lockAndCall,模板类型推导将得到它是一个 int,而 f1 期望接收的是 std::share_ptr<Widget> ,匹配失败。对于第二个调用也是类似的。第三个调用传入 nullptr 是没有问题的,当 nullptr 被传入时,ptr 的类型被推导为 std::nullptr_tstd::nullptr_t 可以隐式转化为任意类型指针,因此能够和 f3 匹配成功。

上面的例子使用 nullptr 的优势非常明显,因此使用 nullptr 代替 0 和 NULL 吧!

总结一下:

  • 相较于 0 和 NULL,优先使用 nullptr
  • 避免对整数类型和指针类型的重载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值