nullptr是一个C++11引入的关键字,用于表示空指针。与NULL相比,nullptr具有更好的类型安全性和可读性。nullptr是一个特殊的字面值常量,可以隐式地转换为任何指针类型,而NULL则是一个宏定义,通常被定义为0或((void*)0)。由于nullptr的类型信息明确,它可以避免一些潜在的类型转换错误。此外,nullptr还可以与模板和函数重载等高级特性更好地配合使用,提供更灵活的编程选项。因此,在C++11及以后的版本中,推荐使用nullptr来表示空指针。

658
我们现在有了许多新特性的C++11。一个有趣而令人困惑的特性(至少对我来说)是新的nullptr
嗯,不再需要那个讨厌的宏NULL了。
int* x = nullptr;
myclass* obj = nullptr;

不过,我还是不明白nullptr是如何工作的。例如,维基百科文章中提到:

C++11通过引入一个新的关键字来作为特殊的空指针常量:nullptr。它的类型是nullptr_t,可以隐式转换并与任何指针类型或指向成员的指针类型进行比较。它不能隐式转换或与整数类型进行比较,除了bool类型。

它既是一个关键字又是一个类型的实例,这是怎么回事呢?

此外,除了维基百科的例子,你还有其他例子能说明nullptr比传统的0更好吗?


28
相关事实:nullptr在C++/CLI中还用于表示托管句柄的空引用。 - Mehrdad Afshari
7
nullptr_t 是否保证只有一个成员 nullptr?如果一个函数返回 nullptr_t,那么编译器已经知道将返回哪个值,无论函数体是什么? - Aaron McDaid
9
std::nullptr_t 可以被实例化,但是所有的实例都与 nullptr 相同,因为该类型被定义为 typedef decltype(nullptr) nullptr_t。我相信该类型存在的主要原因是为了函数可以进行重载,以便特别捕获 nullptr。示例可参见此处 - Justin Time - Reinstate Monica
8
0从来不是空指针,空指针是通过将0字面量强制转换为指针类型得到的指针,根据定义,它不指向任何现有对象。 - Swift - Friday Pie
7
重点在于表达意图! - curiousguy
显示剩余2条评论
14个回答

445
它既是关键字,也是类型的一个实例吗?
这并不奇怪。 truefalse 都是关键字,并且作为文字值它们有一个类型 (bool)。 nullptr 是一个指向类型为 std::nullptr_t 的指针字面量,它是一个 prvalue(无法使用 & 取其地址)。
关于指针转换的第 4.10 节表示,类型为 std::nullptr_t 的 prvalue 是空指针常量,并且整数空指针常量可以转换为 std::nullptr_t。反方向是不允许的。这允许为指针和整数重载一个函数,并传递 nullptr 来选择指针版本。传递 NULL0 将混淆选择 int 版本。
nullptr_t 转换为整数类型需要一个 reinterpret_cast,并具有与将 (void*)0 转换为整数类型相同的语义(映射实现定义)。reinterpret_cast 无法将 nullptr_t 转换为任何指针类型。如果可能,请依赖隐式转换或使用 static_cast
标准要求 sizeof(nullptr_t)sizeof(void*)

1
哦,看了一下,似乎条件运算符不能在像 cond ? nullptr : 0; 这样的情况下将 0 转换为 nullptr。已从我的答案中删除。 - Johannes Schaub - litb
101
请注意,NULL 甚至不能保证是 0。它可以是 0L,这种情况下调用 void f(int); void f(char *); 将会有歧义。nullptr 总是优先选择指针版本,并且永远不会调用 int 版本。另外请注意,nullptr 可以转换为 bool(草案在第 4.12 节说明)。 - Johannes Schaub - litb
28
@Steve,不会调用int版本。但是f(0L)是有歧义的,因为long -> intlong -> void*的成本都是一样的。因此,如果在您的编译器上,NULL是0L,那么调用f(NULL)将会有歧义。当然,使用nullptr就没有这个问题了。 - Johannes Schaub - litb
4.12 中 nullptr 到 bool 的转换仅适用于直接初始化,因此 if(nullptr) 不被包括在内。 - Rastaban
2
在C++中,不能将其定义为(void*)0。但它可以被定义为任何任意的空指针常量,其中任何值为0和nullptr的整数常量都符合条件。所以,绝对不是will,而是can。(你忘了@我了..) - Deduplicator
显示剩余3条评论

118

C++11中为什么要使用nullptr?它是什么?为什么NULL不够用?

C++专家Alex Allain在这里(我加粗了重点)完美地解释了:

...imagine you have the following two function declarations:

void func(int n); 
void func(char *s);
 
func( NULL ); // guess which function gets called?

Although it looks like the second function will be called--you are, after all, passing in what seems to be a pointer--it's really the first function that will be called! The trouble is that because NULL is 0, and 0 is an integer, the first version of func will be called instead. This is the kind of thing that, yes, doesn't happen all the time, but when it does happen, is extremely frustrating and confusing. If you didn't know the details of what is going on, it might well look like a compiler bug. A language feature that looks like a compiler bug is, well, not something you want.

Enter nullptr. In C++11, nullptr is a new keyword that can (and should!) be used to represent NULL pointers; in other words, wherever you were writing NULL before, you should use nullptr instead. It's no more clear to you, the programmer, (everyone knows what NULL means), but it's more explicit to the compiler, which will no longer see 0s everywhere being used to have special meaning when used as a pointer.

Allain在文章中提到:

无论如何,C++11的经验法则就是:只要你以前使用NULL,现在就改用nullptr

(我的话):

最后,不要忘记nullptr是一个对象——一个类。它可以在任何以前使用NULL的地方使用,但如果出于某种原因需要其类型,则可以使用decltype(nullptr)提取其类型,或者直接描述为std::nullptr_t,这只是decltype(nullptr)的一个typedef,如下所示:

在头文件<cstddef>中定义:

见:

  1. https://en.cppreference.com/w/cpp/types/nullptr_t
  2. https://en.cppreference.com/w/cpp/header/cstddef
namespace std
{
typedef decltype(nullptr) nullptr_t; // (since C++11)
// OR (same thing, but using the C++ keyword `using` instead of the C and C++ 
// keyword `typedef`):
using nullptr_t = decltype(nullptr); // (since C++11)
} // namespace std

参考资料:

  1. Cprogramming.com:C++11中更好的类型 - nullptr、枚举类(强类型枚举)和cstdint
  2. https://en.cppreference.com/w/cpp/language/decltype
  3. https://en.cppreference.com/w/cpp/types/nullptr_t
  4. https://en.cppreference.com/w/cpp/header/cstddef
  5. https://en.cppreference.com/w/cpp/keyword/using
  6. https://en.cppreference.com/w/cpp/keyword/typedef

64

来自nullptr: A Type-safe and Clear-Cut Null Pointer:

C++09新增了一个关键词nullptr,它指定了一个作为通用空指针字面量的rvalue常量,替代了有缺陷和弱类型的字面量0和臭名昭著的NULL宏。nullptr因此结束了30多年的困扰、歧义和Bugs。以下几节将介绍nullptr工具并展示它如何治愈NULL和0的不足。

其他参考:


18
2011年8月之前,C++09不是被称为C++0x吗? - Michael Dorst
3
@anthropomorphic 嗯,这就是它的目的。在C++0x还在进展中时就开始使用它了,因为不知道它是在2008年还是2009年完成。请注意,它实际上成为了C++0B,也就是C++11。详情请见http://www.stroustrup.com/C++11FAQ.html。 - mxmlnkn

37

当您有一个可以接收多个类型指针的函数时,使用NULL调用它是模棱两可的。目前解决这个问题的方法非常不规范,即接受一个整数并假设它是NULL

template <class T>
class ptr {
    T* p_;
    public:
        ptr(T* p) : p_(p) {}

        template <class U>
        ptr(U* u) : p_(dynamic_cast<T*>(u)) { }

        // Without this ptr<T> p(NULL) would be ambiguous
        ptr(int null) : p_(NULL)  { assert(null == NULL); }
};

在C++11中,您可以对nullptr_t进行重载,这样"ptr p(42);"将成为编译时错误,而不是运行时的"assert"。
ptr(std::nullptr_t) : p_(nullptr)  {  }

如果将NULL定义为0L会怎样? - L. F.

11

nullptr 不能被赋值给整数类型,比如 int,只能被赋值给指针类型; 这包括内置的指针类型如 int *ptr 或智能指针类型如 std::shared_ptr<T>

我认为这是一个重要的区别,因为 NULL 仍然可以被赋值给整数类型和指针类型,因为 NULL 是宏展开成 0,既可以作为 int 的初始值,也可以作为指针。


请注意,这个答案是错误的。NULL不能保证被扩展为0 - L. F.

7
此外,除了维基百科的例子之外,您是否有另一个示例可以说明nullptr比传统的0更好呢?
是的。这也是我们生产代码中发生的(简化后的)真实世界示例。它之所以突出,是因为当交叉编译到具有不同寄存器宽度的平台时(仍然不确定为什么只有从x86_64交叉编译到x86时才会发出警告warning: converting to non-pointer type 'int' from NULL),gcc能够发出警告:
考虑以下代码(C++03):
#include <iostream>

struct B {};

struct A
{
    operator B*() {return 0;}
    operator bool() {return true;}
};

int main()
{
    A a;
    B* pb = 0;
    typedef void* null_ptr_t;
    null_ptr_t null = 0;

    std::cout << "(a == pb): " << (a == pb) << std::endl;
    std::cout << "(a == 0): " << (a == 0) << std::endl; // no warning
    std::cout << "(a == NULL): " << (a == NULL) << std::endl; // warns sometimes
    std::cout << "(a == null): " << (a == null) << std::endl;
}

它会产生以下输出:
(a == pb): 1
(a == 0): 0
(a == NULL): 0
(a == null): 1

我不明白在使用nullptr(和C++11)时,这会如何改善。如果将pb设置为nullptr,则第一个比较仍然为true(将苹果与梨子进行比较...)。第二种情况更糟:如果将a与nullptr进行比较,则会将a转换为B*,然后再次评估为true(在其被强制转换为bool之前,表达式评估为false)。整个事情让我想起JavaScript,我想知道我们是否将来会在C++中获得===。 - Nils

6

其他语言有一些保留字是类型的实例。例如,Python:

>>> None = 5
  File "<stdin>", line 1
SyntaxError: assignment to None
>>> type(None)
<type 'NoneType'>

这实际上是一个相当接近的比较,因为None通常用于尚未初始化的内容,但同时像None == 0这样的比较是错误的。

另一方面,在普通C语言中,NULL == 0将返回true,因为NULL只是返回0的宏,而0始终是无效地址(据我所知)。


5
NULL 是一个宏,它会扩展为零。将常量零强制转换为指针会产生空指针。空指针不一定是零(但通常是),零也不总是无效地址。非常量零强制转换为指针不一定为空,将空指针强制转换为整数也不一定为零。希望我没有漏掉任何内容。参考链接:http://c-faq.com/null/null2.html - Samuel Edwin Ward

5

首先,让我给你一个不太复杂的nullptr_t实现。

struct nullptr_t 
{
    void operator&() const = delete;  // Can't take address of nullptr

    template<class T>
    inline operator T*() const { return 0; }

    template<class C, class T>
    inline operator T C::*() const { return 0; }
};

nullptr_t nullptr;

nullptr 是一个微妙的 返回类型解析器 惯用语,根据它所分配给的实例的类型自动推断出正确类型的空指针。

int *ptr = nullptr;                // OK
void (C::*method_ptr)() = nullptr; // OK
  • 正如您所见,当将nullptr分配给整数指针时,会创建一个模板化转换函数的int类型实例化。方法指针也是一样。
  • 通过利用模板功能,我们实际上每次创建适当类型的空指针,这样做时,就会进行新的类型分配。
  • 由于nullptr是值为零的整数字面量,因此您无法使用其地址,我们通过删除&操作符来实现这一点。

为什么我们首先需要nullptr

  • 您可以看到传统的NULL存在以下问题:

1️⃣ 隐式转换

char *str = NULL; // Implicit conversion from void * to char *
int i = NULL;     // OK, but `i` is not pointer type

2️⃣ 函数调用的歧义性

void func(int) {}
void func(int*){}
void func(bool){}

func(NULL);     // Which one to call?
  • 编译产生以下错误:
error: call to 'func' is ambiguous
    func(NULL);
    ^~~~
note: candidate function void func(bool){}
                              ^
note: candidate function void func(int*){}
                              ^
note: candidate function void func(int){}
                              ^
1 error generated.
compiler exit status 1

3️⃣ 构造函数重载

struct String
{
    String(uint32_t)    {   /* size of string */    }
    String(const char*) {       /* string */        }
};

String s1( NULL );
String s2( 5 );
  • 在这种情况下,您需要进行显式转换(即,String s((char*)0)))。

3
假设您有一个函数(f),它被重载以接受int和char*。在C++ 11之前,如果您想使用空指针调用它,并且使用了NULL(即值为0),那么您将调用重载int的函数:
void f(int);
void f(char*);

void g() 
{
  f(0); // Calls f(int).
  f(NULL); // Equals to f(0). Calls f(int).
}

这可能不是你想要的。C++11通过nullptr解决了这个问题;现在你可以写如下代码:

void g()
{
  f(nullptr); //calls f(char*)
}

3

它是一个关键字,因为标准将其指定为关键字。;-) 根据最新的公共草案(n2914)

2.14.7 Pointer literals [lex.nullptr]

pointer-literal:
nullptr

The pointer literal is the keyword nullptr. It is an rvalue of type std::nullptr_t.

这很有用,因为它不会隐式转换为整数值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接