C++ 使用 nullptr 有什么好处?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13816385/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 17:44:30  来源:igfitidea点击:

What are the advantages of using nullptr?

c++c++11nullc++-faqnullptr

提问by Mark Garcia

This piece of code conceptuallydoes 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 nullptrover assigning them the values NULLor 0?

所以,什么是分配球的优势,nullptr在赋予它们的值NULL0

回答by Nawaz

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)会调用!这是一个大问题1,不是吗?

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 nullptris deduced as nullptr_t, so you can write this:

因为在模板中, 的类型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++, NULLis 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)被调用。

回答by Alok Save

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

C++11 引入了nullptr,它被称为Null指针常量,它提高了类型安全性解决了与现有实现相关的空指针常量不同的歧义情况NULL。能够了解nullptr. 我们首先需要了解什么是它NULL以及与之相关的问题是什么。



What is NULLexactly?

究竟是NULL什么?

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

Pre C++11NULL用于表示没有值的指针或不指向任何有效内容的指针。与流行的概念相反,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 0or (void *)0. But in C++ there is a subtle difference.

C++ 标准将 NULL 定义为在某些标准库头文件中定义的实现定义宏。NULL 的起源来自 C,C++ 继承自 C。C 标准将 NULL 定义为0or (void *)0。但是在 C++ 中有一个微妙的区别。

C++ could not accept this specification as it is. Unlike C, C++ is a strongly typed language (C does not require explicit cast 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++ 不能接受这个规范。与 C 不同,C++ 是一种强类型语言(C 不需要显式转换void*为任何类型,而 C++ 要求显式转换)。这使得 C 标准指定的 NULL 定义在许多 C++ 表达式中无用。例如:

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

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

如果 NULL 定义为(void *)0,则上述表达式都不起作用。

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

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

因此,与 C 不同,C++ 标准要求将 NULL 定义为数字文字00L.



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

那么,当我们已经NULL有了另一个空指针常量时,还需要什么?

Though the 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 定义,但这个定义也有其自身的问题。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 intversion gets called. This is because NULL is a numeric literal.

显然,意图似乎是调用char*作为参数的版本,但由于输出显示了采用int版本的函数被调用。这是因为 NULL 是数字文字。

Furthermore, since it is implementation-defined whether NULL is 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.
  • Case 2:calls doSomething(int)but maybe char*version was be desired because 0IS also a null pointer.
  • Case 3:If NULLis defined as 0, calls doSomething(int)when perhaps doSomething(char *)was intended, perhaps resulting in logic error at runtime. If NULLis defined as 0L, the call is ambiguous and results in compilation error.
  • 情况 1:doSomething(char *)按预期调用。
  • 情况 2:调用doSomething(int)但可能需要char*版本,因为它0也是一个空指针。
  • 情况 3:如果NULL定义为0,则doSomething(int)可能doSomething(char *)在预期时调用,可能会导致运行时出现逻辑错误。如果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 nullptrand how does it avoid the problems of NULL?

那么什么是nullptr以及如何避免NULL?

C++11 introduces a new keyword nullptrto serve as null pointer constant. Unlike NULL, its behavior is not implementation-defined. It is not a 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 its 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
  • Case 2:Not Ok - Property 3
  • Case 3:OK - Property 3
  • Case 4:No confusion - Calls char *version, Property 2 & 3
  • 案例 1:好的 - 属性 2
  • 案例 2:不好 - 属性 3
  • 案例 3:好的 - 属性 3
  • 案例 4:没有混淆 - 调用char *版本,属性 2 和 3

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

因此引入 nullptr 避免了老式 NULL 的所有问题。

How and where should you use nullptr?

您应该如何以及在哪里使用nullptr

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

C++11 的经验法则是nullptr只要你过去会使用 NULL就开始使用。



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 宏 NULL
C++11 标准:18.2 类型
C++11 标准:4.10 指针转换
C99 标准:6.3.2.3 指针

回答by Puppy

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 typeis what is special, and the type can indeed propagate. In fact, the MSVC team had to introduce nullptrahead of schedule after they implemented rvalue references and then discovered this pitfall for themselves.

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

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

还有一些其他nullptr极端案例可以让生活更轻松 - 但这不是核心案例,因为演员阵容可以解决这些问题。考虑

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); }

回答by Ajay yadav

Basics of nullptr

nullptr 的基础知识

std::nullptr_tis 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.

std::nullptr_t是空指针字面量 nullptr 的类型。它是一个 prvalue/rvalue 类型std::nullptr_t。存在从 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——而不是空指针”)之间的矛盾。指针”)。

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 nullptrover NULL(0)when using auto for return type

的另一个优点nullptrNULL(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 返回什么,则可能不清楚 result 是指针类型还是整数类型。毕竟,0(测试的结果)可以采用任何一种方式。另一方面,如果您看到以下内容,

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

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

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

Advantage 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 lockAndCallinstead of multiple definition lockAndCallF1, lockAndCallF2 & lockAndCallF3for 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)

详细分析为什么编译失败 for lockAndCall(f1, f1m, 0) & lockAndCall(f3, f3m, nullptr)not forlockAndCall(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 f1expects. The 0 passed in the call to lockAndCallwas 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 lockAndCallwith 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,这与预期的std::shared_ptr<int>参数不兼容f1。调用中传递的 0lockAndCall旨在表示空指针,但实际传递的是 int。尝试将此 int 作为 a 传递给 f1std::shared_ptr<int>是一个类型错误。对lockAndCallwith 0的调用失败,因为在模板内部,一个 int 被传递给一个需要std::shared_ptr<int>.

The analysis for the call involving NULLis essentially the same. When NULLis 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,会发生类型错误,它期望得到一个std::unique_ptr<int>

In contrast, the call involving nullptrhas no trouble. When nullptris passed to lockAndCall, the type for ptris deduced to be std::nullptr_t. When ptris passed to f3, there's an implicit conversion from std::nullptr_tto int*, because std::nullptr_timplicitly converts to all pointer types.

相比之下,涉及的调用nullptr没有问题。当nullptr传递给 时lockAndCallptr推导出for 的类型为std::nullptr_t。当ptr传递给 时f3,有一个从std::nullptr_tto的隐式转换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

回答by iammilind

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

nullptr以您展示示例的方式没有直接优势。
但是考虑一下您有 2 个同名函数的情况;1 需要int和另一个int*

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

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

如果你想foo(int*)通过传递一个NULL来调用,那么方法是:

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

nullptrmakes it more easy and intuitive:

nullptr使它更容易和直观

foo(nullptr);

Additional linkfrom 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)

回答by Angew is no longer proud of SO

Just as others have already said, its primary advantage lies in overloads. And while explicit intvs. 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*.

不编译:Cannot convert int to MyClass*

回答by leftaroundabout

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.

IMO 比那些重载问题更重要:在深度嵌套的模板构造中,很难不忘记类型,并且提供显式签名是一项相当大的努力。因此,对于您使用的所有内容,越精确地专注于预期目的越好,它将减少对显式签名的需求,并允许编译器在出现问题时生成更有洞察力的错误消息。