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

不需要讨厌的宏NULL了。

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

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

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

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

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


当前回答

根据cppreference, nullptr是一个关键字,它:

表示指针字面量。它是std::nullptr_t类型的prvalue。 存在从nullptr到空指针值的隐式转换 任何指针类型和任何指向成员类型的指针。类似的转换 存在于任何空指针常量,其中包括类型的值 std::nullptr_t以及宏NULL。

因此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转换为特定的指针类型,或者使用nullptr_t为func提供重载/专门化。


Advantages of using nulptr:

避免函数重载之间的模糊 使您能够进行模板专门化 更安全,直观和富有表现力的代码,例如if (ptr == nullptr)而不是if (ptr == 0)

其他回答

为什么在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)的类型定义,如下所示:

定义在头<cstddef>:

See:

https://en.cppreference.com/w/cpp/types/nullptr_t 和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

引用:

c++ 11中更好的类型- nullptr,枚举类(强类型枚举)和cstdint https://en.cppreference.com/w/cpp/language/decltype https://en.cppreference.com/w/cpp/types/nullptr_t https://en.cppreference.com/w/cpp/header/cstddef https://en.cppreference.com/w/cpp/keyword/using https://en.cppreference.com/w/cpp/keyword/typedef

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

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

当一个函数可以接收指向多个类型的指针时,用NULL来调用它是不明确的。这种工作方式现在是非常hack的接受一个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);将是编译时错误,而不是运行时断言。

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

这是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结果作为哨兵值传递给普通指针的方式相比,这显然会减少“永远不要忘记,它可能是一个炸弹”的因素。

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

是的。这也是在我们的生产代码中出现的一个(简化的)真实例子。它之所以突出,是因为gcc能够在交叉编译到具有不同寄存器宽度的平台时发出警告(仍然不确定为什么只有在从x86_64交叉编译到x86时,警告警告:从NULL转换为非指针类型'int'):

考虑以下代码(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