C++ NULL與nullptr的區別


C與C++中空指針的區別

在C里面,由於處處都要使用指針,所以導致NULL遍布各地。我們先來看C99是怎么定義NULL的:

NULL can be defined as any null pointer constant. Thus existing code can retain definitions of NULL as 0 or 0L, but an implementation may also choose to define it as (void*)0. This latter form of definition is convenient on architectures where sizeof(void*) does not equal the size of any integer type. It has never been wise to use NULL in place of an arbitrary pointer as a function argument, however, since pointers to different types need not be the same size. The library avoids this problem by providing special macros for the arguments to signal, the one library function that might see a null function pointer.

可見,在C99里面,NULL可以被定義為0或者0L(32位和64位的區別),或者直接就是由0或者0L轉成的成void*。
 
接下來我們來看下C++ 14(N4296)中所定義的null pointer。

A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t.

 

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

 

A prvalue of type pointer to cv T,” where T is an object type, can be converted to a prvalue of type pointer to cv void”. The result of converting a non-null pointer value of a pointer to object type to a pointer to cv void represents the address of the same byte in memory as the original pointer value. The null pointer value is converted to the null pointer value of the destination type.

 

A prvalue of type pointer to cv D”, where D is a class type, can be converted to a prvalue of type pointer to cv B”, where B is a base class (Clause 10) of D. If B is an inaccessible (Clause 11) or ambiguous (10.2base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.

 
第一句話就表明了,在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<T> foo(new T(arg...))這樣用的,但是其實用make_shared創建shared_ptr的方法更為高效,因為我們從模板中可以看到shared_ptr的占用空間其實是要比T要大的(為了保存引用計數的東西)。如果我們使用shared_ptr<T> 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);

 

 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM