使用nullptr有什么好处?

Mar*_*cia 158 c++ null c++-faq nullptr c++11

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

int* p1 = nullptr;
int* p2 = NULL;
int* p3 = 0;
Run Code Online (Sandbox Code Playgroud)

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

Naw*_*waz 175

在该代码中,似乎没有优势.但请考虑以下重载函数:

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

f(NULL);  //which function will be called?
Run Code Online (Sandbox Code Playgroud)

将调用哪个函数?当然,这里的意图是打电话f(char const *),但实际上f(int)会被召唤!那是个大问题1,不是吗?

所以,解决这些问题的方法是使用nullptr:

f(nullptr); //first function is called
Run Code Online (Sandbox Code Playgroud)

当然,这不是唯一的优势nullptr.这是另一个:

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

template<>
struct something<nullptr_t, nullptr>{};  //partial specialization for nullptr
Run Code Online (Sandbox Code Playgroud)

因为在模板中,类型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!!!
Run Code Online (Sandbox Code Playgroud)

1.在C++中,NULL被定义为#define NULL 0,所以它基本上int就是为什么f(int)被调用.

  • 你的脚注似乎倒退了.标准要求"NULL"具有整数类型,这就是为什么它通常被定义为"0"或"0L".另外我不确定我喜欢'nullptr_t`重载,因为它只使用`nullptr`捕获**调用,而不是使用不同类型的空指针,如`(void*)0`.但是我可以相信它有一些用途,即使它只是保存你定义一个你自己的单值占位符类型来表示"无". (9认同)
  • @MarkGarcia,这可能会有所帮助:http://stackoverflow.com/questions/13665349/what-is-a-proper-use-case-of-stdnullptr-t-template-parameters (2认同)
  • @DeadMG:我的回答有什么不正确的地方?`f(nullptr)` 不会调用预期的函数?动机不止一种。在未来的几年里,程序员自己可以发现许多其他有用的东西。所以你不能说“nullptr”*只有一种真正的用法*。 (2认同)

Alo*_*ave 86

C++ 11介绍nullptr,它被称为Null指针常量,它改进了类型安全性解决了与现有的依赖于实现的空指针常量不同的模糊情况NULL.能够理解的优点nullptr.我们首先需要了解NULL与之相关的问题是什么以及与之相关的问题.


究竟是NULL什么?

Pre C++ 11 NULL用于表示没有值的指针或指向无效的指针.与流行的概念相反,NULL它不是C++中的关键字.它是标准库头中定义的标识符.简而言之,如果NULL不包含一些标准库头文件,则无法使用.考虑示例程序:

int main()
{ 
    int *ptr = NULL;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

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

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

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
Run Code Online (Sandbox Code Playgroud)

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

  • 情况1:无法编译,因为需要自动void *转换std::string.
  • 情况2:不会编译,因为void *需要从指向成员函数的指针转换.

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


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

虽然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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

In Int version
Run Code Online (Sandbox Code Playgroud)

显然,意图似乎是调用char*作为参数的版本,但是输出显示了调用int版本的函数.这是因为NULL是一个数字文字.

此外,由于NULL是0或0L是实现定义的,因此在函数重载解析中会有很多混淆.

示例程序:

#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
}
Run Code Online (Sandbox Code Playgroud)

分析上面的片段:

  • 案例1:doSomething(char *)按预期调用.
  • 情况2:调用doSomething(int)但可能需要char*版本,因为0IS也是空指针.
  • 情况3:如果NULL被定义为0,doSomething(int)可能doSomething(char *)是预期的调用,可能在运行时导致逻辑错误.如果NULL定义为0L,则调用不明确并导致编译错误.

因此,根据实现,相同的代码可以给出各种结果,这显然是不希望的.当然,C++标准委员会想要纠正这一点,这是nullptr的主要动机.


那么nullptr它是什么以及如何避免这些问题NULL呢?

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

属性1:它有自己的类型std::nullptr_t,
属性2:它是可隐式转换的,可与任何指针类型或指向成员类型的类型相比,但
属性3:它不可隐式转换或与积分类型相比,除了bool.

请考虑以下示例:

#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;
}
Run Code Online (Sandbox Code Playgroud)

在上面的程序中,

  • 案例1:好的 - 财产2
  • 案例2:不好 - 财产3
  • 案例3:好的 - 财产3
  • 案例4:没有混淆 - 调用char *版本,属性2和3

因此,引入nullptr避免了旧的NULL的所有问题.

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

C++ 11的经验法则只是nullptr在过去使用NULL时开始使用.


标准参考:

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


Pup*_*ppy 23

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

考虑:

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

简单地说,0是一个特殊,但值不能通过系统传播 - 只有类型可以.转发功能是必不可少的,0不能处理它们.因此,它是绝对必要的引进nullptr,那里的类型是什么,是特殊的,类型的确可以传播.实际上,MSVC团队nullptr在实施右值引用之后必须提前引入,然后自己发现了这个陷阱.

还有一些其他角落的情况nullptr可以让生活更轻松 - 但它不是核心案例,因为演员可以解决这些问题.考虑

void f(int);
void f(int*);
int main() { f(0); f(nullptr); }
Run Code Online (Sandbox Code Playgroud)

调用两个单独的重载.另外,考虑一下

void f(int*);
void f(long*);
int main() { f(0); }
Run Code Online (Sandbox Code Playgroud)

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

void f(std::nullptr_t)
int main() { f(nullptr); }
Run Code Online (Sandbox Code Playgroud)

  • 滑稽.答案的一半与其他两个答案相同,根据你是*"非常不正确"*答案! (7认同)

Aja*_*dav 5

nullp的基础知识

std::nullptr_t是null指针文字的类型,nullptr.它是类型的prvalue/rvalue std::nullptr_t.存在从nullptr到任何指针类型的空指针值的隐式转换.

文字0是int,而不是指针.如果C++发现自己在只能使用指针的上下文中看0,那么它会勉强将0解释为空指针,但这是一个后备位置.C++的主要策略是0是int,而不是指针.

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

在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*)
Run Code Online (Sandbox Code Playgroud)

关于那个调用的有趣之处在于源代码的明显含义("我用NULL调用有趣的空指针")和它的实际含义之间的矛盾("我用某种整数调用有趣 - 而不是null指针").

nullptr的优点是它没有整数类型.使用nullptr调用重载函数有趣会调用void*overload(即指针重载),因为nullptr不能被视为任何整数:

fun(nullptr); // calls fun(void*) overload 
Run Code Online (Sandbox Code Playgroud)

使用nullptr而不是0或NULL可以避免重载决策意外.

的另一个优点nullptrNULL(0)使用自动时返回类型

例如,假设您在代码库中遇到此问题:

auto result = findRecord( /* arguments */ );
if (result == 0) {
....
}
Run Code Online (Sandbox Code Playgroud)

如果您碰巧知道(或不能轻易找出)findRecord返回的内容,则可能不清楚结果是指针类型还是整数类型.毕竟,0(测试的结果是什么)可以是任何一种方式.另一方面,如果您看到以下内容,

auto result = findRecord( /* arguments */ );
if (result == nullptr) {
...
}
Run Code Online (Sandbox Code Playgroud)

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

优势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;
}
Run Code Online (Sandbox Code Playgroud)

上面的程序编译并执行成功但是lockAndCallF1,lockAndCallF2和lockAndCallF3都有冗余代码.如果我们可以为所有这些编写模板,那么编写这样的代码是很遗憾的lockAndCallF1, lockAndCallF2 & lockAndCallF3.所以它可以用模板推广.我编写了模板函数lockAndCall而不是lockAndCallF1, lockAndCallF2 & lockAndCallF3冗余代码的多重定义.

代码重新计算如下:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

问题是,当0传递给lockAndCall时,模板类型推导开始计算其类型.0的类型是int,因此这是对lockAndCall调用实例化中的参数ptr的类型.不幸的是,这意味着在lockAndCall内部调用func时,正在传递一个int,并且它与期望的std::shared_ptr<int>参数不兼容f1.在调用中传递的0 lockAndCall用于表示空指针,但实际传递的是int.尝试将此int传递给f1作为std::shared_ptr<int>类型错误.对lockAndCall0 的调用失败,因为在模板内部,int被传递给需要a的函数std::shared_ptr<int>.

对涉及的呼叫的分析NULL基本相同.当NULL被传递到lockAndCall,积分型推导出的参数的ptr,并且当发生了错误类型ptr-an int或类似于int的类型传递给f2,其期望得到一个std::unique_ptr<int>.

相反,涉及的呼叫nullptr没有问题.当nullptr被传递到lockAndCall,该类型ptr推断为std::nullptr_t.当ptr被传递到f3,有从隐式转换std::nullptr_tint*,因为std::nullptr_t隐含全部转换为指针类型.

建议,每当要引用空指针时,请使用nullptr,而不是0或NULL.