nullptr到底是什么?


570

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

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

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

不过,我仍无法了解其nullptr工作原理。例如,维基百科文章说:

C ++ 11通过引入一个新的关键字作为一个独特的空指针常量:nullptr 来纠正此问题。它的类型为nullptr_t,可以隐式转换,并且可以与任何指针类型或指针到成员类型进行比较。除了布尔型,它不能隐式转换或与整数类型媲美。

关键字和类型实例如何?

此外,您是否还有另一个示例(除了Wikipedia之外)nullptr优于旧的示例0


23
相关事实:nullptr还用于表示C ++ / CLI中托管句柄的空引用。
Mehrdad Afshari 2009年

3
使用Visual C ++时,请记住,如果将nullptr与本机C / C ++代码一起使用,然后使用/ clr编译器选项进行编译,则编译器无法确定nullptr是指示本机还是托管的空指针值。为了使您的意图明确,可以使用nullptr指定托管值或使用__nullptr指定本机值。Microsoft已将其实现为组件扩展。
cseder

6
nullptr_t保证只有一个成员,nullptr?因此,如果函数返回了nullptr_t,那么无论函数的主体如何,编译器都知道将返回哪个值?
亚伦·麦克戴德

8
std::nullptr_t可以实例化@AaronMcDaid ,但是所有实例都将相同,nullptr因为类型定义为typedef decltype(nullptr) nullptr_t。我相信类型存在的主要原因是nullptr必要时可以重载函数以专门捕获。请参阅此处的示例。
贾斯汀(Justin Time)-恢复莫妮卡(Monica)

5
0永远不是空指针,空指针是可以通过 零文字转换为指针类型而获得的指针,并且按定义它不指向任何现有对象。
斯威夫特-星期五派

Answers:


403

关键字和类型实例如何?

这不足为奇。这两个truefalse是关键字和文字他们有一个类型(bool)。nullptr是type 的指针文字std::nullptr_t,并且是prvalue(您不能使用来获取其地址&)。

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

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

  • 该标准规定,sizeof(nullptr_t)BE sizeof(void*)


呵呵,看完之后,在我看来条件运算符无法将0转换为nullptr cond ? nullptr : 0;。从我的答案中删除。
Johannes Schaub-litb

88
请注意,NULL甚至不能保证是0。它可以是0L,在这种情况下,对的调用void f(int); void f(char *);将是不明确的。nullptr将始终支持指针版本,而永远不会调用该指针版本int。另请注意,它nullptr 可以转换为bool(草稿在处注明4.12)。
Johannes Schaub-litb

@litb:那么关于f(int)和f(void *)-f(0)仍然是模棱两可的吗?
史蒂夫·弗利

27
@Steve,不,它将调用该int版本。不过f(0L)是模糊的,因为long -> int藏汉因为long -> void*是都同样昂贵。因此,如果0L编译器上为NULL ,则f(NULL)给定这两个函数,调用将是不确定的。nullptr当然不是。
Johannes Schaub-litb

2
@SvenS不得将其定义为(void*)0C ++。但是它可以定义为任意空指针常量,任何值为0且nullptr必须满足的整数常量。因此,绝对可以,但是绝对可以。(您忘了给我ping一下。)
重复数据删除器

60

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

新的C ++ 09 nullptr关键字指定一个右值常量,该常量用作通用的空指针文字,从而替换了有问题的弱类型文字0和臭名昭著的NULL宏。因此,nullptr结束了30多年的尴尬,歧义和错误。以下各节介绍了nullptr工​​具,并显示了它如何解决NULL和0的问题。

其他参考:


17
C ++ 09?是不是在2011年8月之前将其称为C ++ 0x?
Michael Dorst 2013年

2
@拟人化这就是它的目的。使用C ++ 0x仍在进行中,因为不知道它会在2008年还是2009年完成。请注意,它实际上变成了C ++ 0B,表示C ++ 11。参见stroustrup.com/C++11FAQ.html
mxmlnkn

44

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

C ++专家Alex Allain在这里说得很完美(我的强调以粗体显示):

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

void func(int n); 
void func(char *s);

func( NULL ); // guess which function gets called?

尽管看起来将调用第二个函数(毕竟,您传递了似乎是指针的东西),但它实际上是将被调用的第一个函数!问题在于,因为NULL为0,并且0为整数,所以将调用func的第一个版本。是的,这种事情不会一直发生,但是一旦发生,就会非常令人沮丧和困惑。如果您不知道发生了什么事情的详细信息,它很可能看起来像是编译器错误。看起来像编译器错误的语言功能不是您想要的。

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

Allain的文章结尾为:

无论如何,C ++ 11的经验法则就是nullptr只要您NULL过去曾经使用过,就可以开始使用它。

(我的话):

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

参考文献:

  1. Cprogramming.com:C ++ 11中更好的类型-nullptr,枚举类(强类型枚举)和cstdint
  2. https://zh.cppreference.com/w/cpp/language/decltype
  3. https://en.cppreference.com/w/cpp/types/nullptr_t

2
我必须说您的答案被低估了,通过您的示例很容易理解。
MSS

37

当您有一个可以接收多个类型的指针的函数时,用调用NULL是不明确的。现在,通过接受一个int并假设为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); }
};

在in中,C++11您将能够进行重载,nullptr_tptr<T> p(42);将是编译时错误而不是运行时错误assert

ptr(std::nullptr_t) : p_(nullptr)  {  }

如果NULL定义为0L
LF

9

nullptr不能分配给整数类型,例如,int只能分配给指针类型;内置指针类型(例如)int *ptr或智能指针(例如)std::shared_ptr<T>

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


请注意,此答案是错误的。 NULL不能保证扩展到0
LF

6

另外,您是否还有另一个示例(除了Wikipedia之外)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

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

5

好吧,其他语言也保留了作为类型实例的保留字。例如,Python:

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

实际上,这是一个相当接近的比较,因为None通常用于尚未初始化的内容,但与此同时,例如None == 0 false之类的。

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


4
NULL是一个扩展为零的宏,将常量零强制转换为指针会产生一个空指针。空指针不必一定为零(但通常是),零并不总是无效的地址,并且将非常量零强制转换为指针不必一定为空,而将空指针强制转换为整数不必为零。我希望我一切都好,不要忘记任何事情。参考:c-faq.com/null/null2.html
Samuel Edwin Ward

3

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

2.14.7指针文字[lex.nullptr]

pointer-literal:
nullptr

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

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


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*)
}

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;

nullptrReturn Type Resolver惯用法的一个细微示例,它根据分配给它的实例的类型自动推断出正确类型的空指针。

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))

0

0曾经是唯一可用作指针的无强制转换初始化程序的整数值:如果没有强制转换,则无法使用其他整数值初始化指针。您可以将0视为语法上类似于整数文字的consexpr单例。它可以启动任何指针或整数。但令人惊讶的是,您会发现它没有独特的类型:它是一个int。那么0可以初始化指针而1不能初始化指针呢?一个实际的答案是,我们需要一种定义指针空值的方法,并且将指针直接隐式转换为int指针容易出错。因此,0成为史前时代之外真正的怪胎怪兽。 nullptr有人提出将其作为空值的真实单例constexpr表示形式来初始化指针。它不能用于直接初始化整数并消除与定义有关的歧义,可以使用std语法将其定义为库,但从语义上看,它是缺少的核心组件。 现在不推荐使用,除非某些图书馆决定将其定义为。NULL为0时的。nullptrNULLnullptrnullptr


-1

这是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比段错误更友好...)。另一件事情是它看起来并不与存储地址兼容所有。与将空*吊起并将NULL结果作为哨兵值传递给普通指针相比,这显然会减少“永远不会忘记,这可能是炸弹”的因素。


-2

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


2
嗯,内存中空指针的实际值可能不为零,但是C(和C ++)标准要求编译器将整数0文字转换为空指针。
bzim
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.