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

不需要讨厌的宏NULL了。

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

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

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

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

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


当前回答

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

其他回答

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

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

这并不奇怪。true和false都是关键字,作为字面量,它们有一个类型(bool)。Nullptr是一个std::nullptr_t类型的指针字面值,它是一个prvalue(不能使用&获取它的地址)。

4.10 about pointer conversion says that a prvalue of type std::nullptr_t is a null pointer constant, and that an integral null pointer constant can be converted to std::nullptr_t. The opposite direction is not allowed. This allows overloading a function for both pointers and integers, and passing nullptr to select the pointer version. Passing NULL or 0 would confusingly select the int version. A cast of nullptr_t to an integral type needs a reinterpret_cast, and has the same semantics as a cast of (void*)0 to an integral type (mapping implementation defined). A reinterpret_cast cannot convert nullptr_t to any pointer type. Rely on the implicit conversion if possible or use static_cast. The Standard requires that sizeof(nullptr_t) be sizeof(void*).

从nullptr:一个类型安全和明确的空指针:

新的c++ 09 nullptr关键字指定了一个右值常量,用作通用空指针字面量,取代了有bug且弱类型的字面量0和臭名昭著的null宏。因此,Nullptr结束了30多年来的尴尬、歧义和错误。下面几节介绍nullptr功能,并展示它如何补救NULL和0的问题。

其他参考资料:

WikiBooks,带有示例代码。 Stack Overflow:在c++中,指针使用NULL还是0(零)? 模板 谷歌组:comp.lang.c++。有节制的编译器讨论

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

让我首先给您一个简单的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))。