使用nullptr有什么好处?

时间:2022-07-15 16:13:40

This piece of code conceptually does the same thing for the three pointers (safe pointer initialization):

这段代码在概念上对三个指针做了同样的事情(安全的指针初始化):

int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;

And so, what are the advantages of assigning pointers nullptr over assigning them the values NULL or 0?

那么,赋值指针nullptr比赋值为NULL或0有什么好处呢?

7 个解决方案

#1


157  

In that code, there doesn't seem to be an advantage. But consider the following overloaded functions:

在这段代码中,似乎没有什么优势。但请考虑以下重载的函数:

void f(char const *ptr);
void f(int v);

f(NULL);  //which function will be called?

Which function will be called? Of course, the intention here is to call f(char const *), but in reality f(int) will be called! That is a big problem1, isn't it?

调用哪个函数?当然,这里的目的是调用f(char const *),但是在现实中调用f(int) !这是个大问题,不是吗?

So, the solution to such problems is to use nullptr:

因此,解决这类问题的方法是使用nullptr:

f(nullptr); //first function is called

Of course, that is not the only advantage of nullptr. Here is another:

当然,这并不是nullptr的唯一优点。这是另一个问题:

template<typename T, T *ptr>
struct something{};                     //primary template

template<>
struct something<nullptr_t, nullptr>{};  //partial specialization for nullptr

Since in template, the type of nullptr is deduced as nullptr_t, so you can write this:

因为在template中,nullptr的类型被推断为nullptr_t,所以可以这样写:

template<typename T>
void f(T *ptr);   //function to handle non-nullptr argument

void f(nullptr_t); //an overload to handle nullptr argument!!!

1. In C++, NULL is defined as #define NULL 0, so it is basically int, that is why f(int) is called.

1。在c++中,NULL被定义为#define NULL 0,所以它基本上是int型的,这就是为什么要调用f(int)。

#2


76  

C++11 introduces nullptr, it is known as the Null pointer constant and It improves type safety and resolves ambiguous situations unlike the existing implementation dependent null pointer constant NULL. To be able to understand the advantages of nullptr. we first need to understand what is NULL and what are the problems associated with it.

c++ 11引入了nullptr,它被称为空指针常量,它提高了类型安全性,并解决了与现有实现相关的空指针常量空值不同的不明确情况。能够理解nullptr的优点。我们首先需要理解什么是NULL,什么是与它相关的问题。


What is NULL exactly?

Pre C++11 NULL was used to represent a pointer that has no value or pointer that does not point to anything valid. Contrary to the popular notion NULL is not a keyword in C++. It is an identifier defined in standard library headers. In short you cannot use NULL without including some standard library headers. Consider the Sample program:

前c++ 11 NULL用于表示一个指针,该指针没有任何值,也没有指向任何有效的指针。与流行的概念相反,NULL在c++中不是关键字。它是在标准库头中定义的标识符。简而言之,如果不包含一些标准库头,就不能使用NULL。考虑示例程序:

int main()
{ 
    int *ptr = NULL;
    return 0;
}

Output:

输出:

prog.cpp: In function 'int main()':
prog.cpp:3:16: error: 'NULL' was not declared in this scope

The C++ standard defines NULL as an implementation defined macro defined in certain standard library header files. The origin of NULL is from C and C++ inherited it from C. The C standard defined NULL as 0 or (void *)0. But in C++ there is a subtle difference.
C++ could not accept this specification as it is. Unlike C, C++ is a strongly typed language.(C does not require explicit cast while from void* to any type while C++ mandates a explicit cast) .This makes the definition of NULL specified by C standard useless in many C++ expressions, for example:

c++标准将NULL定义为在某些标准库头文件中定义的实现定义的宏。NULL的来源是C, c++从C继承的。C标准将NULL定义为0或(void *)0。但是在c++中有一个微妙的区别。c++不能接受这个规范。与C不同,c++是一种强类型语言。(C不需要显式转换,而从void*转换为任何类型,而c++要求显式转换)。

std::string * str = NULL;         //Case 1
void (A::*ptrFunc) () = &A::doSomething;
if (ptrFunc == NULL) {}           //Case 2

If NULL was defined as (void *)0. Both of above expressions would not work.

如果NULL被定义为(void *)0。以上两种表达都不起作用。

  • Case 1: Will not compile because a automatic cast is needed from void * to std::string.
  • 案例1:将不会编译,因为需要从void *到std::string进行自动转换。
  • Case 2: Will not compile because cast from void * to pointer to member function is needed.
  • 案例2:将不会编译,因为需要从void *转换为指向成员函数的指针。

So unlike C, C++ Standard mandated to define NULL as numeric literal 0 or 0L.

因此,与C不同,c++标准要求将NULL定义为数字文字0或0L。


So what is the need for another null pointer constant when we have NULL already?

Though C++ Standards committee came up with a NULL definition which will work for C++, this definition had its own fair share of problems. NULL worked well enough for almost all scenarios but not all. It gave surprising and erroneous results for certain rare scenarios. For example:

虽然c++标准委员会提出了一个对c++适用的零定义,但是这个定义也有它自己的问题。NULL适用于几乎所有场景,但不是所有场景。在某些罕见的情况下,它给出了令人惊讶和错误的结果。例如:

#include<iostream>
void doSomething(int)
{
    std::cout<<"In Int version";
}
void doSomething(char *)
{
   std::cout<<"In char* version";
}

int main()
{
    doSomething(NULL);
    return 0;
}

Output:

输出:

In Int version

Clearly, the intention seems to be to call the version which takes char* as the argument, but as the output shows the function which takes an int version gets called. This is because NULL is an numeric literal.

显然,目的似乎是调用以char*为参数的版本,但是输出显示了调用int版本的函数。这是因为NULL是一个数值文字。

Furthermore, Since it is implementation defined whether NULL can be 0 or 0L there can lot of confusion in function overload resolution.

此外,由于它是实现定义的NULL可以是0还是0L,因此函数的重载解析会有很多混乱。

Sample Program:

示例程序:

#include <cstddef>

void doSomething(int);
void doSomething(char *);

int main()
{
  doSomething(static_cast <char *>(0));    // Case 1
  doSomething(0);                          // Case 2
  doSomething(NULL)                        // Case 3
}

Analyzing the above snippet:

分析上面的代码片段:

  • Case 1: calls doSomething(char *) as expected
  • 案例1:按预期调用doSomething(char *)
  • Case 2: calls doSomething(int) but maybe char* version was be desired because 0 IS also a null pointer.
  • 情形2:调用doSomething(int),但是可能需要char*版本,因为0也是一个空指针。
  • Case 3: If NULL is defined as 0,
    calls doSomething(int) when perhaps doSomething(char *) was intended, perhaps resulting in logic error at runtime.
    While, if NULL is defined as 0L,
    Call is ambiguous and results in compilation error.
  • 情形3:如果NULL被定义为0,那么在可能需要doSomething(char *)时调用doSomething(int),可能会在运行时导致逻辑错误。而,如果NULL被定义为0L,则调用是不明确的,会导致编译错误。

So depending on implementation the same code can give various outcomes, which is clearly undesired. Naturally, the C++ standards committee wanted to correct this and that is the prime motivation for nullptr.

因此,依赖于实现,相同的代码可以产生不同的结果,这显然是不希望的。自然,c++标准委员会想要纠正这一点,这是nullptr的主要动机。


So what is nullptr and how it avoids problems of NULL?

C++11 introduces a new keyword nullptr to serve as null pointer constant. Unlike NULL its behavior is not implementation defined. It is not an Macro but it has its own type. nullptr has the type std::nullptr_t. C++11 appropriately defines properties for the nullptr to avoid the disadvantages of NULL. To summarize its properties:

c++ 11引入了一个新的关键字nullptr作为空指针常量。与NULL不同,它的行为没有定义实现。它不是一个宏,但它有自己的类型。nullptr具有std::nullptr_t类型。c++ 11适当地定义了nullptr的属性,以避免NULL的缺点。总结它的属性:

Property 1: It has it’s own type std::nullptr_t and
Property 2: It is implicitly convertible and comparable to any pointer type or pointer-to-member type but
Property 3: It is not implicitly convertible or comparable to integral types, except for bool.

属性1:它有自己的类型std::nullptr_t和属性2:它是隐式可转换的,可以与任何指针类型或指针对成员类型相比较,但是属性3:它不是隐式可转换的,也不能与整型相比较,除了bool。

Consider the following example:

考虑下面的例子:

#include<iostream>
void doSomething(int)
{
    std::cout<<"In Int version";
}
void doSomething(char *)
{
   std::cout<<"In char* version";
}

int main()
{
    char *pc = nullptr;      // Case 1
    int i = nullptr;         // Case 2
    bool flag = nullptr;     // Case 3

    doSomething(nullptr);    // Case 4
    return 0;
}

In the above program,

在上面的程序中,

  • Case 1: OK - Property 2
  • 案例1:好的,性质2
  • Case 2: Not Ok - Property 3
  • 情形2:不可以——性质3
  • Case 3: OK - Property 3
  • 案例3:好的,属性3。
  • Case 4: No confusion - Calls char * version, Property 2 & 3
  • 案例4:没有混淆——调用char * version,属性2和属性3

Thus introduction of nullptr avoids all the problems of good old NULL.

因此,nullptr的引入避免了老空的所有问题。

How and where should you use nullptr?

The rule of thumb for C++11 is simply start using nullptr whenever you would have otherwise used NULL in the past.

c++ 11的经验法则是,只要您以前使用NULL,就可以使用nullptr。


Standard References:

标准参考:

C++11 Standard: C.3.2.4 Macro NULL
C++11 Standard: 18.2 Types
C++11 Standard: 4.10 Pointer conversions
C99 Standard: 6.3.2.3 Pointers

C+ 11标准:C.3.2.4宏空C+ 11标准:18.2类型C+ 11标准:4.10指针转换C99标准:6.3.2.3指针

#3


22  

The real motivation here is perfect forwarding.

这里真正的动机是完美的转发。

Consider:

考虑:

void f(int* p);
template<typename T> void forward(T&& t) {
    f(std::forward<T>(t));
}
int main() {
    forward(0); // FAIL
}

Simply put, 0 is a special value, but values cannot propagate through the system- only types can. Forwarding functions are essential, and 0 can't deal with them. Thus, it was absolutely necessary to introduce nullptr, where the type is what is special, and the type can indeed propagate. In fact, the MSVC team had to introduce nullptr ahead of schedule after they implemented rvalue references and then discovered this pitfall for themselves.

简单地说,0是一个特殊值,但是值不能通过系统传播——只有类型可以。转发函数是必不可少的,0不能处理它们。因此,绝对有必要引入nullptr,其中类型是特殊的,类型确实可以传播。事实上,MSVC团队在实现了rvalue引用并发现了这个陷阱之后,不得不提前引入nullptr。

There are a few other corner cases where nullptr can make life easier- but it's not a core case, as a cast can solve these problems. Consider

在其他一些情况下,nullptr可以让事情变得更简单——但它不是一个核心情况,因为cast可以解决这些问题。考虑

void f(int);
void f(int*);
int main() { f(0); f(nullptr); }

Calls two separate overloads. In addition, consider

调用两个单独的过载。此外,考虑

void f(int*);
void f(long*);
int main() { f(0); }

This is ambiguous. But, with nullptr, you can provide

这是模棱两可的。但是,使用nullptr,您可以提供

void f(std::nullptr_t)
int main() { f(nullptr); }

#4


4  

There is no direct advantage of having nullptr in the way you have shown the examples.
But consider a situation where you have 2 functions with same name; 1 takes int and another an int*

使用nullptr并没有直接的优势,就像您展示的示例那样。但是考虑这样一种情况,你有两个名称相同的函数;1取int和另一个int*。

void foo(int);
void foo(int*);

If you want to call foo(int*) by passing a NULL, then the way is:

如果要通过传递NULL调用foo(int*),则方法是:

foo((int*)0); // note: foo(NULL) means foo(0)

nullptr makes it more easy and intuitive:

nullptr让它更简单、更直观:

foo(nullptr);

Additional link from Bjarne's webpage.
Irrelevant but on C++11 side note:

来自Bjarne网站的额外链接。不相关,但在c++ 11边注:

auto p = 0; // makes auto as int
auto p = nullptr; // makes auto as decltype(nullptr)

#5


4  

Just as others have already said, its primary advantage lies in overloads. And while explicit int vs. pointer overloads can be rare, consider standard library functions like std::fill (which has bitten me more than once in C++03):

正如其他人已经说过的,它的主要优势在于过载。虽然显式int和指针重载很少见,但是可以考虑标准库函数,比如std::fill(它在c++ 03中已经被我使用了不止一次):

MyClass *arr[4];
std::fill_n(arr, 4, NULL);

Doesn't compile: Cannot convert int to MyClass*.

不编译:不能将int转换为MyClass*。

#6


4  

Basic's of nullptr

基本的nullptr

std::nullptr_t is the type of the null pointer literal, nullptr. It is a prvalue/rvalue of type std::nullptr_t. There exist implicit conversions from nullptr to null pointer value of any pointer type.

nullptr_t是空指针字面量nullptr的类型。它是std: nullptr_t类型的prvalue/rvalue。存在从nullptr到任何指针类型的空指针值的隐式转换。

The literal 0 is an int, not a pointer. If C++ finds itself looking at 0 in a context where only a pointer can be used, it’ll grudgingly interpret 0 as a null pointer, but that’s a fallback position. C++’s primary policy is that 0 is an int, not a pointer.

文字0是一个整数,而不是一个指针。如果c++发现自己在一个只能使用指针的上下文中查找0,它会勉强地将0解释为空指针,但这是一个回退位置。c++的主要策略是0是一个整数,而不是一个指针。

Advantage 1 - Remove ambiguity when overloading on pointer and integral types

优点1 -在重载指针和整型时消除歧义

In C++98, the primary implication of this was that overloading on pointer and integral types could lead to surprises. Passing 0 or NULL to such overloads never called a pointer overload:

在c++ 98中,主要的含义是对指针和整型的重载可能导致意外。将0或NULL传递给这样的重载从来不会被称为指针过载:

   void fun(int); // two overloads of fun
    void fun(void*);
    fun(0); // calls f(int), not fun(void*)
    fun(NULL); // might not compile, but typically calls fun(int). Never calls fun(void*)

The interesting thing about that call is the contradiction between the apparent meaning of the source code (“I am calling fun with NULL-the null pointer”) and its actual meaning (“I am calling fun with some kind of integer— not the null pointer”).

这个调用的有趣之处在于源代码的明显含义(“我用null指针调用fun”)和它的实际含义(“我用某种整数调用fun——而不是null指针”)之间的矛盾。

nullptr’s advantage is that it doesn’t have an integral type. Calling the overloaded function fun with nullptr calls the void* overload (i.e., the pointer overload), because nullptr can’t be viewed as anything integral:

nullptr的优点是它没有整型。用nullptr调用重载的函数fun调用void*重载(即:,指针重载),因为nullptr不能被视为任何积分:

fun(nullptr); // calls fun(void*) overload 

Using nullptr instead of 0 or NULL thus avoids overload resolution surprises.

使用nullptr而不是0或NULL可以避免过载解析。

Another advantage of nullptr over NULL(0) when using auto for return type

当使用auto作为返回类型时,nullptr比NULL(0)的另一个优点

For example, suppose you encounter this in a code base:

例如,假设您在代码库中遇到这种情况:

auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}

If you don’t happen to know (or can’t easily find out) what findRecord returns, it may not be clear whether result is a pointer type or an integral type. After all, 0 (what result is tested against) could go either way. If you see the following, on the other hand,

如果您不知道(或不容易找到)findRecord返回的内容,那么可能不清楚结果是指针类型还是整数类型。毕竟,0(测试结果)可以是任意一种情况。如果你看到另一方面,

auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}

there’s no ambiguity: result must be a pointer type.

没有歧义:结果必须是指针类型。

Advantage 3

优势3

#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
  //do something
  return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
  //do something
  return 0.0;
}
bool f3(int* pw) // mutex is locked
{

return 0;
}

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;

void lockAndCallF1()
{
        MuxtexGuard g(f1m); // lock mutex for f1
        auto result = f1(static_cast<int>(0)); // pass 0 as null ptr to f1
        cout<< result<<endl;
}

void lockAndCallF2()
{
        MuxtexGuard g(f2m); // lock mutex for f2
        auto result = f2(static_cast<int>(NULL)); // pass NULL as null ptr to f2
        cout<< result<<endl;
}
void lockAndCallF3()
{
        MuxtexGuard g(f3m); // lock mutex for f2
        auto result = f3(nullptr);// pass nullptr as null ptr to f3 
        cout<< result<<endl;
} // unlock mutex
int main()
{
        lockAndCallF1();
        lockAndCallF2();
        lockAndCallF3();
        return 0;
}

Above program compile and executed successfully but lockAndCallF1, lockAndCallF2 & lockAndCallF3 have redundant code. It is pity to write code like this if we can write template for all these lockAndCallF1, lockAndCallF2 & lockAndCallF3. So it can be generalized with template. I have written template function lockAndCall instead of multiple definition lockAndCallF1, lockAndCallF2 & lockAndCallF3 for redundant code.

上述程序编译并成功执行,但lockAndCallF1、lockAndCallF2和lockAndCallF3都有冗余代码。如果我们可以为所有这些lockAndCallF1、lockAndCallF2 & lockAndCallF3编写模板,那么编写这样的代码就太遗憾了。可以用模板推广。我编写了模板函数lockAndCall,而不是多个定义lockAndCallF1, lockAndCallF2 & lockAndCallF3来处理冗余代码。

Code is re-factored as below:

代码重构如下:

#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
  //do something
  return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
  //do something
  return 0.0;
}
bool f3(int* pw) // mutex is locked
{

return 0;
}

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;

template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
//decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
{
        MuxtexGuard g(mutex);
        return func(ptr);
}
int main()
{
        auto result1 = lockAndCall(f1, f1m, 0); //compilation failed 
        //do something
        auto result2 = lockAndCall(f2, f2m, NULL); //compilation failed
        //do something
        auto result3 = lockAndCall(f3, f3m, nullptr);
        //do something
        return 0;
}

Detail analysis why compilation failed for lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr) not for lockAndCall(f3, f3m, nullptr)

详细分析为什么编译失败(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr),而不是lockAndCall(f3, f3m, nullptr)

Why compilation of lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr) failed?

为什么编译lockAndCall(f1, f1m, 0)和lockAndCall(f3, f3m, nullptr)失败?

The problem is that when 0 is passed to lockAndCall, template type deduction kicks in to figure out its type. The type of 0 is int, so that’s the type of the parameter ptr inside the instantiation of this call to lockAndCall. Unfortunately, this means that in the call to func inside lockAndCall, an int is being passed, and that’s not compatible with the std::shared_ptr<int> parameter that f1 expects. The 0 passed in the call to lockAndCall was intended to represent a null pointer, but what actually got passed was int. Trying to pass this int to f1 as a std::shared_ptr<int> is a type error. The call to lockAndCall with 0 fails because inside the template, an int is being passed to a function that requires a std::shared_ptr<int>.

问题是,当0被传递到lockAndCall时,模板类型的演绎会开始计算它的类型。0的类型是int,所以这是对lockAndCall的实例化中的参数ptr的类型。不幸的是,这意味着在lockAndCall中对func的调用中,传递了一个int,这与f1所期望的std::shared_ptr 参数不兼容。在lockAndCall中传递的0表示一个空指针,但是实际传递的是int,尝试将这个int传递给f1作为std: shared_ptr 是一个类型错误。使用0调用lockAndCall失败,因为在模板内部,一个int被传递给需要std::shared_ptr 的函数。

The analysis for the call involving NULL is essentially the same. When NULL is passed to lockAndCall, an integral type is deduced for the parameter ptr, and a type error occurs when ptr—an int or int-like type—is passed to f2, which expects to get a std::unique_ptr<int>.

涉及NULL的调用的分析本质上是相同的。当NULL被传递给lockAndCall时,我们推导出参数ptr的一个积分类型,当ptr -一个int型或类似int型的类型-被传递给f2时发生类型错误,f2期望得到std::unique_ptr

In contrast, the call involving nullptr has no trouble. When nullptr is passed to lockAndCall, the type for ptr is deduced to be std::nullptr_t. When ptr is passed to f3, there’s an implicit conversion from std::nullptr_t to int*, because std::nullptr_t implicitly converts to all pointer types.

相反,涉及nullptr的调用没有问题。当nullptr被传递给lockAndCall时,ptr的类型被推断为std::nullptr_t。当ptr传递给f3时,会有一个从std::nullptr_t到int*的隐式转换,因为std:::nullptr_t隐式地转换为所有指针类型。

It is recommended, Whenever you want to refer to a null pointer, use nullptr, not 0 or NULL.

推荐使用nullptr,而不是0或null。

#7


2  

IMO more important than those overload issues: in deeply nested template constructs, it's hard not to lose track of the types, and giving explicit signatures is quite an endeavour. So for everything that you use, the more precisely focused to the intended purpose, the better, it will reduce the need for explicit signatures and allows the compiler to produce more insightful error messages when something goes wrong.

在我看来,比那些重载问题更重要的是:在深度嵌套的模板构造中,很难不忘记类型,给出显式签名是一项艰巨的任务。因此,对于您使用的所有内容,越精确地集中于预期目的,就越好,它将减少对显式签名的需求,并允许编译器在出错时生成更有洞察力的错误消息。

#1


157  

In that code, there doesn't seem to be an advantage. But consider the following overloaded functions:

在这段代码中,似乎没有什么优势。但请考虑以下重载的函数:

void f(char const *ptr);
void f(int v);

f(NULL);  //which function will be called?

Which function will be called? Of course, the intention here is to call f(char const *), but in reality f(int) will be called! That is a big problem1, isn't it?

调用哪个函数?当然,这里的目的是调用f(char const *),但是在现实中调用f(int) !这是个大问题,不是吗?

So, the solution to such problems is to use nullptr:

因此,解决这类问题的方法是使用nullptr:

f(nullptr); //first function is called

Of course, that is not the only advantage of nullptr. Here is another:

当然,这并不是nullptr的唯一优点。这是另一个问题:

template<typename T, T *ptr>
struct something{};                     //primary template

template<>
struct something<nullptr_t, nullptr>{};  //partial specialization for nullptr

Since in template, the type of nullptr is deduced as nullptr_t, so you can write this:

因为在template中,nullptr的类型被推断为nullptr_t,所以可以这样写:

template<typename T>
void f(T *ptr);   //function to handle non-nullptr argument

void f(nullptr_t); //an overload to handle nullptr argument!!!

1. In C++, NULL is defined as #define NULL 0, so it is basically int, that is why f(int) is called.

1。在c++中,NULL被定义为#define NULL 0,所以它基本上是int型的,这就是为什么要调用f(int)。

#2


76  

C++11 introduces nullptr, it is known as the Null pointer constant and It improves type safety and resolves ambiguous situations unlike the existing implementation dependent null pointer constant NULL. To be able to understand the advantages of nullptr. we first need to understand what is NULL and what are the problems associated with it.

c++ 11引入了nullptr,它被称为空指针常量,它提高了类型安全性,并解决了与现有实现相关的空指针常量空值不同的不明确情况。能够理解nullptr的优点。我们首先需要理解什么是NULL,什么是与它相关的问题。


What is NULL exactly?

Pre C++11 NULL was used to represent a pointer that has no value or pointer that does not point to anything valid. Contrary to the popular notion NULL is not a keyword in C++. It is an identifier defined in standard library headers. In short you cannot use NULL without including some standard library headers. Consider the Sample program:

前c++ 11 NULL用于表示一个指针,该指针没有任何值,也没有指向任何有效的指针。与流行的概念相反,NULL在c++中不是关键字。它是在标准库头中定义的标识符。简而言之,如果不包含一些标准库头,就不能使用NULL。考虑示例程序:

int main()
{ 
    int *ptr = NULL;
    return 0;
}

Output:

输出:

prog.cpp: In function 'int main()':
prog.cpp:3:16: error: 'NULL' was not declared in this scope

The C++ standard defines NULL as an implementation defined macro defined in certain standard library header files. The origin of NULL is from C and C++ inherited it from C. The C standard defined NULL as 0 or (void *)0. But in C++ there is a subtle difference.
C++ could not accept this specification as it is. Unlike C, C++ is a strongly typed language.(C does not require explicit cast while from void* to any type while C++ mandates a explicit cast) .This makes the definition of NULL specified by C standard useless in many C++ expressions, for example:

c++标准将NULL定义为在某些标准库头文件中定义的实现定义的宏。NULL的来源是C, c++从C继承的。C标准将NULL定义为0或(void *)0。但是在c++中有一个微妙的区别。c++不能接受这个规范。与C不同,c++是一种强类型语言。(C不需要显式转换,而从void*转换为任何类型,而c++要求显式转换)。

std::string * str = NULL;         //Case 1
void (A::*ptrFunc) () = &A::doSomething;
if (ptrFunc == NULL) {}           //Case 2

If NULL was defined as (void *)0. Both of above expressions would not work.

如果NULL被定义为(void *)0。以上两种表达都不起作用。

  • Case 1: Will not compile because a automatic cast is needed from void * to std::string.
  • 案例1:将不会编译,因为需要从void *到std::string进行自动转换。
  • Case 2: Will not compile because cast from void * to pointer to member function is needed.
  • 案例2:将不会编译,因为需要从void *转换为指向成员函数的指针。

So unlike C, C++ Standard mandated to define NULL as numeric literal 0 or 0L.

因此,与C不同,c++标准要求将NULL定义为数字文字0或0L。


So what is the need for another null pointer constant when we have NULL already?

Though C++ Standards committee came up with a NULL definition which will work for C++, this definition had its own fair share of problems. NULL worked well enough for almost all scenarios but not all. It gave surprising and erroneous results for certain rare scenarios. For example:

虽然c++标准委员会提出了一个对c++适用的零定义,但是这个定义也有它自己的问题。NULL适用于几乎所有场景,但不是所有场景。在某些罕见的情况下,它给出了令人惊讶和错误的结果。例如:

#include<iostream>
void doSomething(int)
{
    std::cout<<"In Int version";
}
void doSomething(char *)
{
   std::cout<<"In char* version";
}

int main()
{
    doSomething(NULL);
    return 0;
}

Output:

输出:

In Int version

Clearly, the intention seems to be to call the version which takes char* as the argument, but as the output shows the function which takes an int version gets called. This is because NULL is an numeric literal.

显然,目的似乎是调用以char*为参数的版本,但是输出显示了调用int版本的函数。这是因为NULL是一个数值文字。

Furthermore, Since it is implementation defined whether NULL can be 0 or 0L there can lot of confusion in function overload resolution.

此外,由于它是实现定义的NULL可以是0还是0L,因此函数的重载解析会有很多混乱。

Sample Program:

示例程序:

#include <cstddef>

void doSomething(int);
void doSomething(char *);

int main()
{
  doSomething(static_cast <char *>(0));    // Case 1
  doSomething(0);                          // Case 2
  doSomething(NULL)                        // Case 3
}

Analyzing the above snippet:

分析上面的代码片段:

  • Case 1: calls doSomething(char *) as expected
  • 案例1:按预期调用doSomething(char *)
  • Case 2: calls doSomething(int) but maybe char* version was be desired because 0 IS also a null pointer.
  • 情形2:调用doSomething(int),但是可能需要char*版本,因为0也是一个空指针。
  • Case 3: If NULL is defined as 0,
    calls doSomething(int) when perhaps doSomething(char *) was intended, perhaps resulting in logic error at runtime.
    While, if NULL is defined as 0L,
    Call is ambiguous and results in compilation error.
  • 情形3:如果NULL被定义为0,那么在可能需要doSomething(char *)时调用doSomething(int),可能会在运行时导致逻辑错误。而,如果NULL被定义为0L,则调用是不明确的,会导致编译错误。

So depending on implementation the same code can give various outcomes, which is clearly undesired. Naturally, the C++ standards committee wanted to correct this and that is the prime motivation for nullptr.

因此,依赖于实现,相同的代码可以产生不同的结果,这显然是不希望的。自然,c++标准委员会想要纠正这一点,这是nullptr的主要动机。


So what is nullptr and how it avoids problems of NULL?

C++11 introduces a new keyword nullptr to serve as null pointer constant. Unlike NULL its behavior is not implementation defined. It is not an Macro but it has its own type. nullptr has the type std::nullptr_t. C++11 appropriately defines properties for the nullptr to avoid the disadvantages of NULL. To summarize its properties:

c++ 11引入了一个新的关键字nullptr作为空指针常量。与NULL不同,它的行为没有定义实现。它不是一个宏,但它有自己的类型。nullptr具有std::nullptr_t类型。c++ 11适当地定义了nullptr的属性,以避免NULL的缺点。总结它的属性:

Property 1: It has it’s own type std::nullptr_t and
Property 2: It is implicitly convertible and comparable to any pointer type or pointer-to-member type but
Property 3: It is not implicitly convertible or comparable to integral types, except for bool.

属性1:它有自己的类型std::nullptr_t和属性2:它是隐式可转换的,可以与任何指针类型或指针对成员类型相比较,但是属性3:它不是隐式可转换的,也不能与整型相比较,除了bool。

Consider the following example:

考虑下面的例子:

#include<iostream>
void doSomething(int)
{
    std::cout<<"In Int version";
}
void doSomething(char *)
{
   std::cout<<"In char* version";
}

int main()
{
    char *pc = nullptr;      // Case 1
    int i = nullptr;         // Case 2
    bool flag = nullptr;     // Case 3

    doSomething(nullptr);    // Case 4
    return 0;
}

In the above program,

在上面的程序中,

  • Case 1: OK - Property 2
  • 案例1:好的,性质2
  • Case 2: Not Ok - Property 3
  • 情形2:不可以——性质3
  • Case 3: OK - Property 3
  • 案例3:好的,属性3。
  • Case 4: No confusion - Calls char * version, Property 2 & 3
  • 案例4:没有混淆——调用char * version,属性2和属性3

Thus introduction of nullptr avoids all the problems of good old NULL.

因此,nullptr的引入避免了老空的所有问题。

How and where should you use nullptr?

The rule of thumb for C++11 is simply start using nullptr whenever you would have otherwise used NULL in the past.

c++ 11的经验法则是,只要您以前使用NULL,就可以使用nullptr。


Standard References:

标准参考:

C++11 Standard: C.3.2.4 Macro NULL
C++11 Standard: 18.2 Types
C++11 Standard: 4.10 Pointer conversions
C99 Standard: 6.3.2.3 Pointers

C+ 11标准:C.3.2.4宏空C+ 11标准:18.2类型C+ 11标准:4.10指针转换C99标准:6.3.2.3指针

#3


22  

The real motivation here is perfect forwarding.

这里真正的动机是完美的转发。

Consider:

考虑:

void f(int* p);
template<typename T> void forward(T&& t) {
    f(std::forward<T>(t));
}
int main() {
    forward(0); // FAIL
}

Simply put, 0 is a special value, but values cannot propagate through the system- only types can. Forwarding functions are essential, and 0 can't deal with them. Thus, it was absolutely necessary to introduce nullptr, where the type is what is special, and the type can indeed propagate. In fact, the MSVC team had to introduce nullptr ahead of schedule after they implemented rvalue references and then discovered this pitfall for themselves.

简单地说,0是一个特殊值,但是值不能通过系统传播——只有类型可以。转发函数是必不可少的,0不能处理它们。因此,绝对有必要引入nullptr,其中类型是特殊的,类型确实可以传播。事实上,MSVC团队在实现了rvalue引用并发现了这个陷阱之后,不得不提前引入nullptr。

There are a few other corner cases where nullptr can make life easier- but it's not a core case, as a cast can solve these problems. Consider

在其他一些情况下,nullptr可以让事情变得更简单——但它不是一个核心情况,因为cast可以解决这些问题。考虑

void f(int);
void f(int*);
int main() { f(0); f(nullptr); }

Calls two separate overloads. In addition, consider

调用两个单独的过载。此外,考虑

void f(int*);
void f(long*);
int main() { f(0); }

This is ambiguous. But, with nullptr, you can provide

这是模棱两可的。但是,使用nullptr,您可以提供

void f(std::nullptr_t)
int main() { f(nullptr); }

#4


4  

There is no direct advantage of having nullptr in the way you have shown the examples.
But consider a situation where you have 2 functions with same name; 1 takes int and another an int*

使用nullptr并没有直接的优势,就像您展示的示例那样。但是考虑这样一种情况,你有两个名称相同的函数;1取int和另一个int*。

void foo(int);
void foo(int*);

If you want to call foo(int*) by passing a NULL, then the way is:

如果要通过传递NULL调用foo(int*),则方法是:

foo((int*)0); // note: foo(NULL) means foo(0)

nullptr makes it more easy and intuitive:

nullptr让它更简单、更直观:

foo(nullptr);

Additional link from Bjarne's webpage.
Irrelevant but on C++11 side note:

来自Bjarne网站的额外链接。不相关,但在c++ 11边注:

auto p = 0; // makes auto as int
auto p = nullptr; // makes auto as decltype(nullptr)

#5


4  

Just as others have already said, its primary advantage lies in overloads. And while explicit int vs. pointer overloads can be rare, consider standard library functions like std::fill (which has bitten me more than once in C++03):

正如其他人已经说过的,它的主要优势在于过载。虽然显式int和指针重载很少见,但是可以考虑标准库函数,比如std::fill(它在c++ 03中已经被我使用了不止一次):

MyClass *arr[4];
std::fill_n(arr, 4, NULL);

Doesn't compile: Cannot convert int to MyClass*.

不编译:不能将int转换为MyClass*。

#6


4  

Basic's of nullptr

基本的nullptr

std::nullptr_t is the type of the null pointer literal, nullptr. It is a prvalue/rvalue of type std::nullptr_t. There exist implicit conversions from nullptr to null pointer value of any pointer type.

nullptr_t是空指针字面量nullptr的类型。它是std: nullptr_t类型的prvalue/rvalue。存在从nullptr到任何指针类型的空指针值的隐式转换。

The literal 0 is an int, not a pointer. If C++ finds itself looking at 0 in a context where only a pointer can be used, it’ll grudgingly interpret 0 as a null pointer, but that’s a fallback position. C++’s primary policy is that 0 is an int, not a pointer.

文字0是一个整数,而不是一个指针。如果c++发现自己在一个只能使用指针的上下文中查找0,它会勉强地将0解释为空指针,但这是一个回退位置。c++的主要策略是0是一个整数,而不是一个指针。

Advantage 1 - Remove ambiguity when overloading on pointer and integral types

优点1 -在重载指针和整型时消除歧义

In C++98, the primary implication of this was that overloading on pointer and integral types could lead to surprises. Passing 0 or NULL to such overloads never called a pointer overload:

在c++ 98中,主要的含义是对指针和整型的重载可能导致意外。将0或NULL传递给这样的重载从来不会被称为指针过载:

   void fun(int); // two overloads of fun
    void fun(void*);
    fun(0); // calls f(int), not fun(void*)
    fun(NULL); // might not compile, but typically calls fun(int). Never calls fun(void*)

The interesting thing about that call is the contradiction between the apparent meaning of the source code (“I am calling fun with NULL-the null pointer”) and its actual meaning (“I am calling fun with some kind of integer— not the null pointer”).

这个调用的有趣之处在于源代码的明显含义(“我用null指针调用fun”)和它的实际含义(“我用某种整数调用fun——而不是null指针”)之间的矛盾。

nullptr’s advantage is that it doesn’t have an integral type. Calling the overloaded function fun with nullptr calls the void* overload (i.e., the pointer overload), because nullptr can’t be viewed as anything integral:

nullptr的优点是它没有整型。用nullptr调用重载的函数fun调用void*重载(即:,指针重载),因为nullptr不能被视为任何积分:

fun(nullptr); // calls fun(void*) overload 

Using nullptr instead of 0 or NULL thus avoids overload resolution surprises.

使用nullptr而不是0或NULL可以避免过载解析。

Another advantage of nullptr over NULL(0) when using auto for return type

当使用auto作为返回类型时,nullptr比NULL(0)的另一个优点

For example, suppose you encounter this in a code base:

例如,假设您在代码库中遇到这种情况:

auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}

If you don’t happen to know (or can’t easily find out) what findRecord returns, it may not be clear whether result is a pointer type or an integral type. After all, 0 (what result is tested against) could go either way. If you see the following, on the other hand,

如果您不知道(或不容易找到)findRecord返回的内容,那么可能不清楚结果是指针类型还是整数类型。毕竟,0(测试结果)可以是任意一种情况。如果你看到另一方面,

auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}

there’s no ambiguity: result must be a pointer type.

没有歧义:结果必须是指针类型。

Advantage 3

优势3

#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
  //do something
  return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
  //do something
  return 0.0;
}
bool f3(int* pw) // mutex is locked
{

return 0;
}

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;

void lockAndCallF1()
{
        MuxtexGuard g(f1m); // lock mutex for f1
        auto result = f1(static_cast<int>(0)); // pass 0 as null ptr to f1
        cout<< result<<endl;
}

void lockAndCallF2()
{
        MuxtexGuard g(f2m); // lock mutex for f2
        auto result = f2(static_cast<int>(NULL)); // pass NULL as null ptr to f2
        cout<< result<<endl;
}
void lockAndCallF3()
{
        MuxtexGuard g(f3m); // lock mutex for f2
        auto result = f3(nullptr);// pass nullptr as null ptr to f3 
        cout<< result<<endl;
} // unlock mutex
int main()
{
        lockAndCallF1();
        lockAndCallF2();
        lockAndCallF3();
        return 0;
}

Above program compile and executed successfully but lockAndCallF1, lockAndCallF2 & lockAndCallF3 have redundant code. It is pity to write code like this if we can write template for all these lockAndCallF1, lockAndCallF2 & lockAndCallF3. So it can be generalized with template. I have written template function lockAndCall instead of multiple definition lockAndCallF1, lockAndCallF2 & lockAndCallF3 for redundant code.

上述程序编译并成功执行,但lockAndCallF1、lockAndCallF2和lockAndCallF3都有冗余代码。如果我们可以为所有这些lockAndCallF1、lockAndCallF2 & lockAndCallF3编写模板,那么编写这样的代码就太遗憾了。可以用模板推广。我编写了模板函数lockAndCall,而不是多个定义lockAndCallF1, lockAndCallF2 & lockAndCallF3来处理冗余代码。

Code is re-factored as below:

代码重构如下:

#include<iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
int f1(std::shared_ptr<int> spw) // call these only when
{
  //do something
  return 0;
}
double f2(std::unique_ptr<int> upw) // the appropriate
{
  //do something
  return 0.0;
}
bool f3(int* pw) // mutex is locked
{

return 0;
}

std::mutex f1m, f2m, f3m; // mutexes for f1, f2, and f3
using MuxtexGuard = std::lock_guard<std::mutex>;

template<typename FuncType, typename MuxType, typename PtrType>
auto lockAndCall(FuncType func, MuxType& mutex, PtrType ptr) -> decltype(func(ptr))
//decltype(auto) lockAndCall(FuncType func, MuxType& mutex, PtrType ptr)
{
        MuxtexGuard g(mutex);
        return func(ptr);
}
int main()
{
        auto result1 = lockAndCall(f1, f1m, 0); //compilation failed 
        //do something
        auto result2 = lockAndCall(f2, f2m, NULL); //compilation failed
        //do something
        auto result3 = lockAndCall(f3, f3m, nullptr);
        //do something
        return 0;
}

Detail analysis why compilation failed for lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr) not for lockAndCall(f3, f3m, nullptr)

详细分析为什么编译失败(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr),而不是lockAndCall(f3, f3m, nullptr)

Why compilation of lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr) failed?

为什么编译lockAndCall(f1, f1m, 0)和lockAndCall(f3, f3m, nullptr)失败?

The problem is that when 0 is passed to lockAndCall, template type deduction kicks in to figure out its type. The type of 0 is int, so that’s the type of the parameter ptr inside the instantiation of this call to lockAndCall. Unfortunately, this means that in the call to func inside lockAndCall, an int is being passed, and that’s not compatible with the std::shared_ptr<int> parameter that f1 expects. The 0 passed in the call to lockAndCall was intended to represent a null pointer, but what actually got passed was int. Trying to pass this int to f1 as a std::shared_ptr<int> is a type error. The call to lockAndCall with 0 fails because inside the template, an int is being passed to a function that requires a std::shared_ptr<int>.

问题是,当0被传递到lockAndCall时,模板类型的演绎会开始计算它的类型。0的类型是int,所以这是对lockAndCall的实例化中的参数ptr的类型。不幸的是,这意味着在lockAndCall中对func的调用中,传递了一个int,这与f1所期望的std::shared_ptr 参数不兼容。在lockAndCall中传递的0表示一个空指针,但是实际传递的是int,尝试将这个int传递给f1作为std: shared_ptr 是一个类型错误。使用0调用lockAndCall失败,因为在模板内部,一个int被传递给需要std::shared_ptr 的函数。

The analysis for the call involving NULL is essentially the same. When NULL is passed to lockAndCall, an integral type is deduced for the parameter ptr, and a type error occurs when ptr—an int or int-like type—is passed to f2, which expects to get a std::unique_ptr<int>.

涉及NULL的调用的分析本质上是相同的。当NULL被传递给lockAndCall时,我们推导出参数ptr的一个积分类型,当ptr -一个int型或类似int型的类型-被传递给f2时发生类型错误,f2期望得到std::unique_ptr

In contrast, the call involving nullptr has no trouble. When nullptr is passed to lockAndCall, the type for ptr is deduced to be std::nullptr_t. When ptr is passed to f3, there’s an implicit conversion from std::nullptr_t to int*, because std::nullptr_t implicitly converts to all pointer types.

相反,涉及nullptr的调用没有问题。当nullptr被传递给lockAndCall时,ptr的类型被推断为std::nullptr_t。当ptr传递给f3时,会有一个从std::nullptr_t到int*的隐式转换,因为std:::nullptr_t隐式地转换为所有指针类型。

It is recommended, Whenever you want to refer to a null pointer, use nullptr, not 0 or NULL.

推荐使用nullptr,而不是0或null。

#7


2  

IMO more important than those overload issues: in deeply nested template constructs, it's hard not to lose track of the types, and giving explicit signatures is quite an endeavour. So for everything that you use, the more precisely focused to the intended purpose, the better, it will reduce the need for explicit signatures and allows the compiler to produce more insightful error messages when something goes wrong.

在我看来,比那些重载问题更重要的是:在深度嵌套的模板构造中,很难不忘记类型,给出显式签名是一项艰巨的任务。因此,对于您使用的所有内容,越精确地集中于预期目的,就越好,它将减少对显式签名的需求,并允许编译器在出错时生成更有洞察力的错误消息。