C++ NULL与nullptr的区别

C与C++中空指针的区别


在C里面,由于处处都要使用指针,所以导致NULL遍布各地。我们先来看C99是怎么定义NULL的:

NULL can be defined as any null pointer constant.Thusexisting code can retain definitions of<span class="pln">NULL as<span class="lit">0<span class="pln">or<span class="lit">0L<span class="pun">,<span class="pln">but an implementation may also choose to define it as<span class="pun">(<span class="kwd">void<span class="pun">*)<span class="lit">0.<span class="pln"><span class="typ">This<span class="pln">latter</span></span></span></span></span></span></span></span></span></span></span></span></span>``<span class="pln">form of definition is convenient on architectures<span class="kwd">where<span class="pln"><span class="kwd">sizeof<span class="pun">(<span class="kwd">void<span class="pun">*)<span class="pln">does not equal the</span></span></span></span></span></span></span></span>``<span class="pln">size of any integer type<span class="pun">.<span class="pln"><span class="typ">It<span class="pln">has never been wise to use NULL in place of an arbitrary pointer as a</span></span></span></span></span>function argument,however,since pointers to different types need not be the same size.The<span class="pln">library avoids<span class="kwd">this<span class="pln">problem by providing special macros<span class="kwd">for<span class="pln">the arguments to signal<span class="pun">,<span class="pln">the one</span></span></span></span></span></span></span>``<span class="pln">library function that might see a null function pointer<span class="pun">.</span></span>
可见,在C99里面,NULL可以被定义为0或者0L(32位和64位的区别),或者直接就是由0或者0L转成的成void*。接下来我们来看下C++ 14(N4296)中所定义的null pointer。
<span class="pln">A null pointer constant is an integer literal<span class="pun">(<span class="lit">2.13<span class="pun">.<span class="lit">2<span class="pun">)<span class="pln">with value zero or a prvalue of type std<span class="pun">::<span class="typ">nullptr_t<span class="pun">.</span></span></span></span></span></span></span></span></span></span>

<span class="pln">A null pointer constant can be converted to a pointer type<span class="pun">;<span class="pln">the result is the null pointer value of that type</span></span></span>``<span class="pln">and is distinguishable from every other value of object pointer or function pointer type<span class="pun">.<span class="pln"><span class="typ">Such<span class="pln">a conversion</span></span></span></span></span>``<span class="pln">is called a null pointer conversion<span class="pun">.<span class="pln"><span class="typ">Two<span class="pln">null pointer values of the same type shall compare equal<span class="pun">.<span class="pln"><span class="typ">The</span></span></span></span></span></span></span></span>``<span class="pln">conversion of a null pointer constant to a pointer to cv<span class="pun">-<span class="pln">qualified type is a single conversion<span class="pun">,<span class="pln">and not the</span></span></span></span></span>``<span class="pln">sequence of a pointer conversion followed by a qualification conversion<span class="pun">(<span class="lit">4.4<span class="pun">).<span class="pln">A null pointer constant of</span></span></span></span></span>``<span class="pln">integral type can be converted to a prvalue of type std<span class="pun">::<span class="typ">nullptr_t<span class="pun">.<span class="pln"><span class="pun">[<span class="pln"><span class="typ">Note<span class="pun">:<span class="pln"><span class="typ">The<span class="pln">resulting prvalue is not a</span></span></span></span></span></span></span></span></span></span></span></span>``<span class="pln">null pointer value<span class="pun">.<span class="pln"><span class="pun">—<span class="pln">end note<span class="pun">]</span></span></span></span></span></span>

<span class="pln">A prvalue of type<span class="pun">“<span class="pln">pointer to cv T<span class="pun">,”<span class="pln"><span class="kwd">where<span class="pln">T is an object type<span class="pun">,<span class="pln">can be converted to a prvalue of type<span class="pun">“<span class="pln">pointer</span></span></span></span></span></span></span></span></span></span></span>``<span class="pln">to cv<span class="kwd">void<span class="pun">”.<span class="pln"><span class="typ">The<span class="pln">result of converting a non<span class="pun">-<span class="pln">null pointer value of a pointer to object type to a<span class="pun">“<span class="pln">pointer to</span></span></span></span></span></span></span></span></span></span>``<span class="pln">cv<span class="kwd">void<span class="pun">”<span class="pln">represents the address of the same byte in memory as the original pointer value<span class="pun">.<span class="pln"><span class="typ">The<span class="pln">null pointer</span></span></span></span></span></span></span></span>``<span class="pln">value is converted to the null pointer value of the destination type<span class="pun">.</span></span>

<span class="pln">A prvalue of type<span class="pun">“<span class="pln">pointer to cv D<span class="pun">”,<span class="pln"><span class="kwd">where<span class="pln">D is a<span class="kwd">class<span class="pln">type<span class="pun">,<span class="pln">can be converted to a prvalue of type<span class="pun">“<span class="pln">pointer</span></span></span></span></span></span></span></span></span></span></span></span></span>``<span class="pln">to cv B<span class="pun">”,<span class="pln"><span class="kwd">where<span class="pln">B is a base<span class="kwd">class<span class="pln"><span class="pun">(<span class="typ">Clause<span class="pln"><span class="lit">10<span class="pun">)<span class="pln">of D<span class="pun">.<span class="pln"><span class="typ">If<span class="pln">B is an inaccessible<span class="pun">(<span class="typ">Clause<span class="pln"><span class="lit">11<span class="pun">)<span class="pln">or ambiguous<span class="pun">(<span class="lit">10.2<span class="pun">)</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>``<span class="pln">base<span class="kwd">class<span class="pln">of D<span class="pun">,<span class="pln">a program that necessitates<span class="kwd">this<span class="pln">conversion is ill<span class="pun">-<span class="pln">formed<span class="pun">.<span class="pln"><span class="typ">The<span class="pln">result of the conversion is a</span></span></span></span></span></span></span></span></span></span></span></span></span>``<span class="pln">pointer to the base<span class="kwd">class<span class="pln">subobject of the derived<span class="kwd">class<span class="pln">object<span class="pun">.<span class="pln"><span class="typ">The<span class="pln">null pointer value is converted to the</span></span></span></span></span></span></span></span></span>``<span class="pln">null pointer value of the destination type<span class="pun">.</span></span>
第一句话就表明了,在C++中,一个指向空的指针要么是一个字面值整形,要么是一个std::nullptr_t我们再来看VS 2015 中所定义的NULL,就是一个0

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

用nullptr解决C++中NULL所不能解决的问题


前面我们说了,C++中的NULL,其实就是一个0,这会导致很多问题,比如我们可以写一个函数重载:

#include <iostream>
#include <algorithm>
#include <memory>
void fun(int)
{
    std::cout << "fuck1" << std::endl;
}
void fun(void *)
{
    std::cout << "fuck2" << std::endl;
}
int main(int argc, char *argv[])
{
    fun(NULL);
    system("pause");
    return 0;
}

一般来说,我们传进去一个NULL,一般想的是要传一个指针,可是在上面的程序中,我们却调用的是int的版本。但是当我们传的是nullptr时:

int main(int argc, char *argv[])
{
    fun(nullptr);
    system("pause");
    return 0;
}

这个时候调用的是第二个版本了,符合我们的设想,这是因为C++规定nullptr可以转为指针类型。而且是cv void *再来一个例子,也就是我们最常见的模板匹配问题了:

struct Fuck
{
    Fuck(char *){ }
};
int main(int argc, char *argv[])
{
    auto p = std::make_shared<Fuck>(NULL);
    throwing();
    system("pause");
    return 0;
}

这个代码会报错,至于为什么,我们先来分析一下make_shared的模板:

template<class _Ty,
    class... _Types> inline
        shared_ptr<_Ty> make_shared(_Types&&... _Args)
    {    // make a shared_ptr
    _Ref_count_obj<_Ty> *_Rx =
        new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);
    shared_ptr<_Ty> _Ret;
    _Ret._Resetp0(_Rx->_Getptr(), _Rx);
    return (_Ret);
    }
// TEMPLATE CLASS _Ref_count_obj
template<class _Ty>
    class _Ref_count_obj
    : public _Ref_count_base
    {    // handle reference counting for object in control block, no allocator
public:
    template<class... _Types>
        _Ref_count_obj(_Types&&... _Args)
        : _Ref_count_base()
        {    // construct from argument list
        ::new ((void *)&_Storage) _Ty(_STD forward<_Types>(_Args)...);
        }
    _Ty *_Getptr() const
        {    // get pointer
        return ((_Ty *)&_Storage);
        }
private:
    virtual void _Destroy() _NOEXCEPT
        {    // destroy managed resource
        _Getptr()->~_Ty();
        }
    virtual void _Delete_this() _NOEXCEPT
        {    // destroy self
        delete this;
        }
    typename aligned_union<1, _Ty>::type _Storage;
    };

这里多说几句,make_shared的操作是先给_Ref_count_obj<_Ty>类型分配一块内存,然后再placement new,回想一下我们平常使用shared_ptr的时候,都是shared_ptr foo(new T(arg...))这样用的,但是其实用make_shared创建shared_ptr的方法更为高效,因为我们从模板中可以看到shared_ptr的占用空间其实是要比T要大的(为了保存引用计数的东西)。如果我们使用shared_ptr foo(new T(arg...))来构造shared_ptr,那么要先给T分配内存并构造T,然后在分配ref_count的内存,但是如果使用make_shared,那么就会直接给T和ref_count一起分配内存,然后再通过C++11的完美转发把T的构造函数传给make_shared。好现在回到我们这篇博客的主题,为什么传一个NULL会报错呢?这是因为由于C++的NULL就是一个字面值常量0,所以传进去时,会被forward推断成int &&,int &&与char 当然不是一个东西,就会报错。这个时候我们就必须使用nullptr了,nullptr可以转换成void ,然后再隐式转换成char *

auto p = std::make_shared<Fuck>(nullptr);

原文链接: https://www.cnblogs.com/Philip-Tell-Truth/p/6594632.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/251185

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月14日 上午5:05
下一篇 2023年2月14日 上午5:06

相关推荐