C++. 优先选用nullptr,而非0或者NULL

探讨C++中nullptr的引入及其如何解决函数重载决议问题,特别是在与指针和整数类型重载冲突时。文章详细解释了nullptr相较于0和NULL的优势,包括提升代码清晰度、增强模板函数的灵活性,以及避免类型歧义。

首先看下面函数重载的例子:

// f的三个重载版本
void f(int a) {	
	cout << "f(int a)" << endl;
}
void f(bool a) {
    cout<< "f(bool a)" <<endl;
}
void f(void* a) {
	cout << "f(void *a)" << endl;
}

int main() {

    f(0);        // 调用的是f(int)
    f(NULL);     // 可能通不过编译,但一般情况下调用的是f(int), vs版本就是这种一般情况
    return 0;
}

f(NULL)的不确定性是NULL的型别在具体实现中有多种余地的一种反应。打个比方,假如NULL的定义为0L(即long类型中的0),那么这个调用就有多歧义了。因为从long到int,从long到bool以及从long到void*都是一样好的。这个调用的重点是源代码所希望表达的实际的意思,即当我们给重载函数传入NULL参数的时候,我们希望是调用f(void *)函数。所以,对于C98程序员来说,知道原则就是:不要在指针型别和整性型别之间做重载。当然这个原则在C++11中任然成立,因为虽然C++11中新增了nullptr,但有些程序员还是会继续使用0或者NULL。

 nullptr的优点在于,它不具备整性型别。实话实说,他也不具备指针型别,但你可以把他想成一种任意型别的指针。nullptr的实际类型是std::nullptr_t,并且,在一个漂亮的循环定一下,std::nullptr_t被定义为nullptr类型。型别std;:nullptr_t可以隐式转化到所有的裸指针型别,这就是为什么nullptr可以扮演所有型别指针的原因。

这里比如:
class A; //自定义类型
void f(A *a);
如果在程序中调用f(nullptr),那么编译器就会将其转换为f((A *)0);

调用重载函数f时传入nullptr会调用f(void *)那个重载版本,因为nullptr无法视作任何一种整性。因此使用nullptr就解决了重载决议的问题,但这不是它的全部优点。它还可以提升代码的清晰度,尤其在涉及auto变量的时候。

//代码块一
auto result = findRecord(/* 实参* /)
if(result == 0) {
    ...
}

// 代码块二
auto result = findRecord(/* 实参 */)
if(result == nullptr) {
    ...
}

对于这两段代码,当我们看到代码块一的时候,如果编译器没有智能提示,那么要知道result的类型,就必须查看findRecord函数定义的返回型别,而代码块二就直接很清晰的显示result是一个指针型别的变量。

nullptr在有模板的前提下表现是最亮眼的。考虑下面这个例子:

int f1(std::shared_ptr<Widget> spw);
double f2(std::unique_ptr<Widget> upw);
bool f3(Widget* pw);

调用这些函数本传入空指针的情况是这样的:

auto result = f1(0);
auto result = f2(NULL);
auto result = f3(nullptr);

前两个调用未能使用nullptr虽然略有遗憾,但这段代码都是可以运行的,也算是有个交代了。

我们将这三个函数进行模板化:

tenplate<class FuncType,
         class PtrType>
auto WrapperAndCall(FuncType func,
                    PtrType ptr) -> decltype(func(ptr))
{
    return func(ptr);
}

这里的函数后面的 -> decltype(func(ptr))是函数返回类型。这里用例子做一点说明:

// 程序中调用
auto result = wrapperAndCall(f1, nullptr);
//那么在编译阶段编译器怎么解释呢?
#if 0
    编译器首先会推导出FuncType的型别是:int (std::shared_ptr<Widget> *)函数指针型别
    接着推到住PtrType型别是:std::nullptr_t

    那么后面的->decltype(func(ptr)) 就是:f1(nullptr) 推导出返回值类型为int
#endif

在使用过程中,调用者可能会写出如下三种代码:

auto result = wrapperAndCall(f1, 0); //错误

auto result = wrapperAndCall(f2, NULL); // 错误

auto result = wrapperAndCall(f3, nullptr); // 正确

这里就拿第二种错误调用方式做一个说明吧,func在编译阶段会被推导为:f2, 而ptr却会被推导为int 的0,也就是话说最后实际调用是这样的:f2((int)0), 参数类型检查自然会报错。

最后,就以以下两点基本原则作为结尾吧:

  1. 相对于0或者NULL,优先选用nullptr
  2. 避免在整性和指针型别之间重载
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值