描述
与0或NULL不同,nullptr字面值明确表示空地址值。
nullptr关键字是类型std::nullptr_t的prvalue(纯右值),表示与主机平台上的空地址对应的和实现相关的定义的位模式。nullptr和其他类型为std::nullptr_t的值,以及整数字面值0和宏NULL,可以隐式地转换为任何指针或指向成员的指针类型:
#include <cstddef> // NULL
int data; //nonmember data
int* pi0 = &data; // Initialize with non-null address.
int* pi1 = nullptr; // Initialize with null address.
int* pi2 = NULL; // " " " "
int* pi3 = 0; // " " " "
double f(int x); //nonmember function
double (*pf0)(int) = &f; //Initialize with non-null address.
double (*pf1)(int) = nullptr; //Initialize with null address.
struct S
{
short d_data; //member data
float g(int y); //member function
};
short S::*pmd0 = &S::d_data; //Initialize with non-null address.
short S::*pmd1 = nullptr; //Initialize with null address.
float (S::*pmf0)(int) = &S::g; //Initialize with non-null address.
float (S::*pmf1)(int) = nullptr; //Initialize with null address.
因为std::nullptr_t是一种独特的类型,所以可以基于它自身进行重载:
#include <cstddef> // std::nullptr_t
void g(void*); // (1)
void g(int); // (2)
void g(std::nullptr_t); // (3)
void f()
{
char buf[]="hello";
g(buf); // OK, (1) void g(void*)
g(0); // OK, (2) void g(int)
g(nullptr); // OK, (3) void g(std::nullptr_t)
g(NULL); // Error, ambiguous --- (1), (2), or (3)
}
1. 改善类型安全
在C++11之前的代码库中,使用NULL宏是一种常见的方法,主要向用户表示宏传递的字面值是专门表示空地址,而不是字面上的整型值0。在C标准中,宏NULL被定义为和实现相关的定义的整型或void*常量。与C不同,C++禁止从void*转换到任意指针类型,在C++11之前,将NULL定义为“整数类型的右值,计算值为零”。任何整数字面值,例如0、0L、0U和0LLU,都满足此准则。然而,从类型安全的角度来看,它的基于实现的定义使得使用NULL比原始字面值0更适合来表示空指针。值得注意的是,在C++11中,NULL的定义已经扩展到理论上允许nullptr作为符合标准的定义;然而,在撰写本文时,没有主要的编译器供应商这样做。
作为nullptr提供的添加类型安全的一个具体说明,假设某大型软件公司的编码标准要求通过输出参数返回值(而不是通过return语句),这样总是通过指向可修改对象的指针返回。通过参数返回的函数通常会将函数的返回值用来表达状态。代码库中的一个函数可能会将输出参数的本地指针变量赋值为零,以指示并确保不再写入更多的东西。下面的函数说明了三种不同的方法:
int illustrativeFunction(int* x) // pointer to modifiable integer
{
// ...
if (/*...*/)
{
x = 0; // OK, set pointer x to null address.
x = NULL; // OK, set pointer x to null address.
x = nullptr; // Bug, set pointer x to null address.
}
// ...
return 0; // success
}
假设现在函数签名发生改变(如组织的编码标准改变了),需要通过引用而非指针:
int illustrativeFunction(int& x) // reference to modifiable integer
{
// ...
if (/*...*/)
{
x=0; // OK, always compiles; makes what x refers to 0
x=NULL; // OK, implementation defined; might warn
x= nullptr; // Error, always a compile time error
}
// ...
return0; // SUCCESS
}
正如上面的示例所示,我们如何表示空地址是很重要的:
-
0一一可移植的所有实现,但只保证最小的类型安全。
-
NULL一一作为一个宏实现,如果有添加的类型安全,也是基于特定平台的。
-
nullptr一一可移植到所有实现并且是完全类型安全的。
使用nullptr而不是0或NULL来表示空地址,最大化了类型的安全性和可读性,同时避免了宏和基于实现定义的行为。
2. 消除重载解决解析时(int)0与(T*)0的歧义
NULL的平台依赖性在调用函数的重载仅接受指针或整型作为相同的位置参数时提出了额外的挑战,情况可能是这样的,例如在设计糟糕的第三方库中:
void uglyLibraryFunction(int* p); // (1)
void uglyLibraryFunction(int 1); // (2)
用0调用这个函数总是会调用重载(2),但这可能并不总是普通客户所期望的:
void f()
{
uglyLibraryFunction(8); // unambiguously invokes (2)
uglyLibraryFunction((int*) 0); // unambiguously invokes (1)
uglyLibraryFunction(nullptr); // unambiguously invokes (1)
uglyLibraryFunction(NULL); // Might invoke (1), (2), or be ambiguous;
// implementation-defined
uglyLibraryFunction(0u); // Error, ambiguous call on all platforms
)
当这种有问题的重载不可避免时,nullptr特别有用,因为它避兔了显式的强制转换。请注意,显式地将0强制转换为一个适当类型的指针一一而不是void*一一曾经被一些人认为是一种最佳实践,特别是在C中。
3.字面值空指针的重载
作为一个独特的类型,std::nullptr_t本身可以参与一个重载集:
#include <cstddef> // std::nullptr_t
void f(int* v); // (1)
void f(std::nullptr_t); // (2)
void g()
{
int* ptr = nullptr;
f(ptr); // unambiguously invokes (1)
f(nullptr); // unambiguously invokes (2)
}
鉴于nullptr可以相对容易地转换为具有相同空地址值的类型指针,当用于控制关键行为时,这种重载是可疑的。尽管如此,我们可以设想这种使用,例如,在传递空地址时帮助编译时诊断,否会导致运行时错误:
std::size_t strlen(const char* s);
// The behavior is undefined unless s is nullterminated.
std::size_t strlen(std::nullptr_t) = delete;
// The function is not defined but still participates in overload resolution.
nullptr的另一种安全的用途是避免空指针检查。但是,对于客户端在编译时知道该地址为空的情况,更好的方式通常是避免在运行时测试空指针。
总结
本文详解介绍了C++11的一种实用特性:nullptr空指针字面值关键字。C++11与C++14还有很多其他实用的特性,想要了解详细内容的话,推荐您阅读《现代C++安全》一书。
本文摘编自《现代C++安全》(书号9787111760290),经出版方授权发布,转载请保留文章来源。
《现代C++安全》一书作者基于多年从事大型软件项目开发的经验,以模块化形式精心组织,阐述了如何有效地利用现代C++的新特性和强大的语言功能,同时避开其中的潜在陷阱。本书还总结了C++社区近十年来应用C++11和C++14新特性的经验,通过使用来自真实代码库的示例说明每个新特性和关键问题,且给出了每个新特性的用例和潜在缺陷,可帮助读者在多样化的大型软件开发环境中做出高效且安全的设计决策。本书对于C++开发人员、团队领导者和技术经理具有很高的参考价值。
作者简介
约翰·拉科斯(John Lakos),哥伦比亚大学计算机科学专业和电气工程专业双博士,本科获得麻省理工学院数学和计算机科学双学士学位,是彭博社全球C++软件开发的高级架构师和导师,并拥有多项软件专利。他还曾在Mentor Graphics公司负责开发大型软件框架和高级ICCAD应用程序。
维托里奥·罗密欧(Vittorio Romeo),彭博社的高级软件工程师,负责构建关键任务的C++中间件和C++的相关培训。他还是许多开源C++库和游戏的创建者。
罗斯蒂斯拉夫·赫列布尼科夫(Rostislav Khlebnikov),博士,彭博社BDE团队的高级软件工程师,负责其中高性能C++基础软件的开发、HTTP/2通信库,以及改进BDE库与标准库词汇类型的互操作性等工作。
利斯代尔·梅雷迪斯(Alisdair Meredith),彭博社BDE团队的高级软件工程师,C++标准委员会的长期成员,并在2010年—2015年担任该委员会图书馆工作组主席。
译者简介
刘晓光,现任南开大学计算机学院、网络空间安全学院副院长,天津市政协委员,曾获宝钢优秀教师奖。主要研究领域包括搜索引擎、云存储和区块链系统等。与百度、华为等建立科研合作,相关成果已经应用于企业的实际产品。
原文始发于微信公众号(Urkc安全):深入剖析 C++11 超实用特性:nullptr 空指针字面值关键字全解
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论