什么是nullptr?

时间:2009-08-15 16:47:32

标签: c++ pointers c++11 nullptr

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

嗯,不再需要令人讨厌的宏NULL

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

尽管如此,我还没有了解nullptr的工作原理。例如,Wikipedia article说:

  

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

它是一个关键字和一个类型的实例?

另外,你有另一个例子(在维基百科旁边),其中nullptr优于好的0吗?

14 个答案:

答案 0 :(得分:377)

  

它是一个关键字和一个类型的实例?

这并不奇怪。 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 :(得分:56)

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

  

新的C ++ 09 nullptr关键字指定一个rvalue常量,用作通用空指针文字,替换错误和弱类型的文字0和臭名昭着的NULL宏。因此,nullptr结束了超过30年的尴尬,模棱两可和错误。以下部分介绍了nullptr工​​具,并展示了如何解决NULL和0的问题。

其他参考资料:

答案 2 :(得分:35)

当你有一个可以接收指向多个类型的指针的函数时,用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中,您可以在nullptr_t上重载,这样ptr<T> p(42);将是编译时错误,而不是运行时assert

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

答案 3 :(得分:23)

为什么C ++ 11中的nullptr?它是什么?为什么NULL不够?

C ++专家Alex Allain says it perfectly here

&#34; ...假设您有以下两个函数声明:

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意味着什么),但它对编译器来说更明确,这将是当用作指针时,不再看到任何地方被用来具有特殊含义。&#34;

答案 4 :(得分:9)

nullptr无法分配给integral type,例如int,只能分配给pointer类型;内置指针类型(如int *ptr)或智能指针(如std::shared_ptr<T>

我认为这是一个重要的区别因为NULL仍然可以分配给integral typepointer,因为NULL是一个扩展为0的宏它既可以作为int的初始值,也可以作为pointer的初始值。

答案 5 :(得分:4)

好吧,其他语言保留了类型实例的单词。例如,Python:

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

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

另一方面,在普通C中,NULL == 0将返回真正的IIRC,因为NULL只是一个返回0的宏,它总是一个无效的地址(AFAIK)。

答案 6 :(得分:4)

  

另外,你有另一个例子(在维基百科旁边),其中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

答案 7 :(得分:3)

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

  

2.14.7指针文字[lex.nullptr]

pointer-literal:
nullptr
     

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

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

答案 8 :(得分: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*)
}

答案 9 :(得分:2)

首先让我为您提供简单的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类型实例。方法指针也是如此。
  • 通过这种方式,利用模板功能,我们实际上每次都在创建适当类型的null指针,即进行新的类型分配。
  • 由于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))

答案 10 :(得分:0)

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

答案 11 :(得分:0)

根据cppreferencenullptr是一个关键字:

表示指针文字。它是类型为 std::nullptr_t 的prvalue。 从nullptr到的空指针值存在 隐式转换 任何指针类型 任何指向成员类型的指针 。相似的转换 对于任何包含以下类型的值的空指针常量都存在 std::nullptr_t以及宏NULL

nullptr将隐式转换为任何指针类型,但转换为整数。 NULL是一个宏,它是实现定义的空指针常量。通常这样定义:

#define NULL 0

即整数。

因此:

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

nullptr可以避免两个函数重载时产生歧义:

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

func(NULL)之所以调用1),是因为NULL是一个整数。 func(nullptr)之所以调用2),是因为nullptr不是整数,并且会隐式转换为任何指针类型。

使用nulptr的优点:

  • 避免函数重载之间的歧义
  • 使您能够进行模板专门化
  • 更安全,直观和更具表现力的代码,例如if (ptr == nullptr)代替if (ptr == 0)

答案 12 :(得分:-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结果作为哨兵值传递给普通指针相比,这显然会减少“永远不会忘记,这可能是炸弹”的因素。

答案 13 :(得分:-2)

NULL need not to be 0. As long you use always NULL and never 0, NULL can be any value. Asuming you programme a von Neuman Microcontroller with flat memory, that has its interrupt vektors at 0. If NULL is 0 and something writes at a NULL Pointer the Microcontroller crashes. If NULL is lets say 1024 and at 1024 there is a reserved variable, the write won't crash it, and you can detect NULL Pointer assignments from inside the programme. This is Pointless on PCs, but for space probes, military or medical equipment it is important not to crash.