现在c++ 11有了许多新特性。一个有趣而令人困惑的(至少对我来说)是新的nullptr。

不需要讨厌的宏NULL了。

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

不过,我还是不明白nullptr是如何工作的。例如,维基百科的一篇文章说:

c++ 11通过引入一个新的关键字作为区分空指针常量nullptr来纠正这一点。它的类型为nullptr_t,可隐式转换,可与任何指针类型或指针到成员类型相比较。它不能隐式转换,也不能与整型相比,bool类型除外。

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

此外,你是否有另一个例子(除了维基百科的一个),其中nullptr优于好旧的0?


当前回答

让我首先给您一个简单的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是一个值为0的整型字面值,你不能使用它的地址,这是我们通过删除&操作符实现的。

为什么我们首先需要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))。

其他回答

让我首先给您一个简单的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是一个值为0的整型字面值,你不能使用它的地址,这是我们通过删除&操作符实现的。

为什么我们首先需要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))。

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

2.14.7指针字面量[lex.nullptr] pointer-literal: nullptr 指针字面值是关键字nullptr。它是std::nullptr_t类型的右值。

它很有用,因为它没有隐式地转换成一个整数值。

Nullptr不能赋值给整型类型,比如int型,只能赋值给指针类型;内置指针类型,如int *ptr或智能指针,如std::shared_ptr<T>

我相信这是一个重要的区别,因为NULL仍然可以被赋给整型和指针,因为NULL是一个扩展到0的宏,可以作为int的初始值以及指针。

这是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/* '可以发现很多内容)

一个突出的东西是操作符* overload(返回0比分段故障友好得多……) 另一件事是,它看起来与存储地址根本不兼容。与抛出void*和将NULL结果作为哨兵值传递给普通指针的方式相比,这显然会减少“永远不要忘记,它可能是一个炸弹”的因素。

其他语言有保留词,它们是类型的实例。例如,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)。