现在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))。

其他回答

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

此外,你是否有另一个例子(除了维基百科的一个),其中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

当一个函数可以接收指向多个类型的指针时,用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)  {  }

假设你有一个重载的函数(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*)
}

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