现在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) { }