618

我们现在拥有具有许多新功能的 C++11。一个有趣且令人困惑的(至少对我而言)是新的nullptr.

好吧,不再需要讨厌的宏了NULL

int* x = nullptr;
myclass* obj = nullptr;

不过,我不知道如何nullptr工作。例如,维基百科文章说:

C++11 通过引入一个新的关键字来作为一个可区分的空指针常量来纠正这个问题:nullptr。它的类型为 nullptr_t,可隐式转换并与任何指针类型或指向成员的指针类型相当。除了 bool 之外,它不能隐式转换或与整数类型相比较。

它如何是关键字和类型的实例?

另外,您是否有另一个示例(除了维基百科)nullptr优于旧的0

4

14 回答 14

429

它如何是关键字和类型的实例?

这并不奇怪。true和都是false关键字,作为文字,它们有一个类型 ( bool)。nullptr是一个类型的指针文字std::nullptr_t,它是一个纯右值(你不能使用它来获取它的地址&)。

  • 4.10关于指针转换说类型的prvaluestd::nullptr_t是一个空指针常量,并且一个整数空指针常量可以转换为std::nullptr_t。不允许相反的方向。这允许为指针和整数重载一个函数,并传递nullptr给选择指针版本。通过NULL0会混淆选择int版本。

  • 转换nullptr_t为整数类型需要一个, 并且与转换为整数类型(映射实现定义)reinterpret_cast具有相同的语义。(void*)0Areinterpret_cast不能转换nullptr_t为任何指针类型。如果可能,请依赖隐式转换或使用static_cast.

  • sizeof(nullptr_t)该标准要求sizeof(void*).

于 2009-08-15T17:06:52.770 回答
83

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

C++ 专家Alex Alllain 在这里说得很完美(我的重点加粗了):

...假设您有以下两个函数声明:

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

虽然看起来第二个函数会被调用——毕竟你是在传递一个看起来像指针的东西——但它确实是第一个被调用的函数!问题是因为NULL是0,而0是整数,所以会调用第一个版本的func。是的,这种事情不会一直发生,但是当它发生时,会非常令人沮丧和困惑。如果你不知道发生了什么的细节,它很可能看起来像一个编译器错误。看起来像编译器错误的语言功能并不是您想要的。

输入空指针。在 C++11 中,nullptr 是一个新关键字,可以(而且应该!)用于表示 NULL 指针;换句话说,无论你以前在哪里写 NULL,你都应该使用 nullptr 。程序员,这对你来说不是更清楚,(每个人都知道 NULL 的含义),但它对编译器来说更明确,当用作指针时,它将不再看到到处都有特殊含义的 0。

艾伦以他的文章结尾:

不管这一切——C++11 的经验法则就是nullptr在你过去使用过的任何时候开始使用NULL

(我的话):

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

在标题中定义<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
于 2018-03-07T06:18:31.433 回答
65

来自nullptr:类型安全且清晰的空指针

新的 C++09 nullptr 关键字指定了一个右值常量,用作通用空指针文字,取代了有缺陷的弱类型文字 0 和臭名昭著的 NULL 宏。因此,nullptr 结束了 30 多年的尴尬、模棱两可和错误。以下部分介绍了 nullptr 工具,并展示了它如何解决 NULL 和 0 的问题。

其他参考:

于 2009-08-15T17:02:18.363 回答
37

当您有一个可以接收指向多个类型的指针的函数时,用它来调用它NULL是模棱两可的。通过接受一个 int 并假设它是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您将能够重载 onnullptr_t因此这ptr<T> p(42);将是编译时错误而不是运行时错误assert

ptr(std::nullptr_t) : p_(nullptr)  {  }
于 2009-08-16T06:46:58.347 回答
10

nullptr不能分配给整数类型,例如 anint但只能分配给指针类型;可以是内置指针类型,例如,int *ptr也可以是智能指针,例如std::shared_ptr<T>

我相信这是一个重要的区别,因为NULL仍然可以将其分配给整数类型和指针,因为NULL扩展的宏0既可以用作 an 的初始值,也可以用作int指针。

于 2013-01-29T13:12:14.827 回答
6

好吧,其他语言有保留字,它们是类型的实例。以 Python 为例:

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

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

另一方面,在纯 C 中, NULL == 0将返回 true IIRC,因为NULL它只是一个返回 0 的宏,它始终是一个无效地址(AFAIK)。

于 2009-08-15T16:52:28.400 回答
6

另外,您是否有另一个示例(除了维基百科)nullptr优于旧 0 的示例?

是的。这也是我们生产代码中发生的(简化的)真实示例。它之所以脱颖而出,是因为 gcc 在交叉编译到具有不同寄存器宽度的平台时能够发出警告(仍然不确定为什么只有在从 x86_64 交叉编译到 x86 时才会发出警告warning: converting to non-pointer type 'int' from NULL):

考虑这段代码(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
于 2013-09-25T20:31:24.023 回答
3

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

2.14.7 指针文字 [lex.nullptr]

pointer-literal:
nullptr

指针文字是关键字nullptr。它是类型的右值std::nullptr_t

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

于 2009-08-15T17:00:47.243 回答
2

假设您有一个函数 (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*)
}
于 2017-10-20T21:14:58.270 回答
1

让我先给你一个简单的实现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)).
于 2020-04-13T03:36:39.570 回答
0

0 曾经是唯一可以用作指针的无强制转换初始化器的整数值:您不能在没有强制转换的情况下使用其他整数值初始化指针。您可以将 0 视为语法上类似于整数文字的 conexpr 单例。它可以启动任何指针或整数。但令人惊讶的是,您会发现它没有不同的类型:它是一个int. 那么为什么 0 可以初始化指针而 1 不能呢?一个实际的答案是我们需要一种定义指针空值的方法,并且直接隐式转换为int指针容易出错。于是0变成了史前时代真正的怪胎怪兽。 nullptr被提议是一个真正的单例 constexpr 表示空值来初始化指针。它不能用于直接初始化整数并消除以NULL0定义所涉及的歧义。nullptr可以使用 std 语法将其定义为库,但在语义上看起来是缺少的核心组件。 NULL现在不赞成使用nullptr,除非某些库决定将其定义为nullptr.

于 2018-06-16T07:33:10.143 回答
0

这是 LLVM 标头。

// -*- C++ -*-
//===--------------------------- __nullptr --------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_NULLPTR
#define _LIBCPP_NULLPTR

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

#ifdef _LIBCPP_HAS_NO_NULLPTR

_LIBCPP_BEGIN_NAMESPACE_STD

struct _LIBCPP_TEMPLATE_VIS nullptr_t
{
    void* __lx;

    struct __nat {int __for_bool_;};

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t() : __lx(0) {}
    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t(int __nat::*) : __lx(0) {}

    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator int __nat::*() const {return 0;}

    template <class _Tp>
        _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
        operator _Tp* () const {return 0;}

    template <class _Tp, class _Up>
        _LIBCPP_INLINE_VISIBILITY
        operator _Tp _Up::* () const {return 0;}

    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator==(nullptr_t, nullptr_t) {return true;}
    friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator!=(nullptr_t, nullptr_t) {return false;}
};

inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t __get_nullptr_t() {return nullptr_t(0);}

#define nullptr _VSTD::__get_nullptr_t()

_LIBCPP_END_NAMESPACE_STD

#else  // _LIBCPP_HAS_NO_NULLPTR

namespace std
{
    typedef decltype(nullptr) nullptr_t;
}

#endif  // _LIBCPP_HAS_NO_NULLPTR

#endif  // _LIBCPP_NULLPTR

(很快就可以发现很多东西grep -r /usr/include/*`

跳出来的一件事是运算符*重载(返回 0 比 segfaulting 友好得多......)。另一件事是它看起来与存储地址完全不兼容。与它如何使用 void*'s 并将 NULL 结果作为标记值传递给普通指针相比,这显然会减少“永远不要忘记,它可能是炸弹”的因素。

于 2019-05-06T22:24:32.287 回答
0

根据cppreferencenullptr是一个关键字:

表示指针文字。它是类型的prvalue std::nullptr_t。存在从 nullptr 到 任何指针类型的空指针值和任何指向成员类型的指针的隐式转换。任何空指针常量都存在类似的转换,包括类型值和 宏。std::nullptr_tNULL

nullptr不同类型的值也是如此std::nullptr_t,而不是int。它隐式转换为任何指针类型的空指针值。这个神奇的事情发生在你的幕后,你不必担心它的实现。 NULL但是,它是一个宏,它是一个实现定义的空指针常量。它通常是这样定义的:

#define NULL 0

即一个整数。

这是一个微妙但重要的区别,可以避免歧义。

例如:

int i = NULL;     //OK
int i = nullptr;  //error
int* p = NULL;    //OK
int* p = nullptr; //OK

当你有两个这样的函数重载时:

void func(int x);   //1)
void func(int* x);  //2)

func(NULL)调用 1) 因为NULL是整数。 func(nullptr)调用 2) 因为nullptr隐式转换为类型的指针int*

此外,如果您看到这样的声明:

auto result = findRecord( /* arguments */ );

if (result == nullptr)
{
 ...
}

而且你不能轻易找出findRecord返回的内容,你可以确定它result一定是指针类型;nullptr使这更具可读性。

在推断的上下文中,事情的工作方式略有不同。如果你有这样的模板函数:

template<typename T>
void func(T *ptr)
{
    ...
}

你尝试用以下方式调用它nullptr

func(nullptr);

你会得到一个编译器错误,因为nullptr它的类型是nullptr_t. 您必须要么显式nullptr转换为特定的指针类型,要么为funcwith提供重载/特化nullptr_t


使用 nulptr 的优点:
  • 避免函数重载之间的歧义
  • 使您能够进行模板专业化
  • 更安全、直观和富有表现力的代码,例如,if (ptr == nullptr)而不是if (ptr == 0)
于 2020-09-23T22:15:06.390 回答
-2

NULL 不必为 0。只要您始终使用 NULL 而从不使用 0,NULL 可以是任何值。假设您对具有平坦内存的冯诺依曼微控制器进行编程,其中断向量为 0。如果 NULL 为 0,并且在 NULL 指针处写入某些内容,则微控制器崩溃。如果 NULL 可以说是 1024 并且在 1024 处有一个保留变量,则写入不会使其崩溃,并且您可以从程序内部检测 NULL 指针分配。这在 PC 上毫无意义,但对于太空探测器、军事或医疗设备来说,重要的是不要崩溃。

于 2015-07-31T20:31:51.737 回答