標籤:

C++ 11為什麼引入nullptr?

NULL是一個宏,它的值是0,nullptr書上說是C++11加入的專門用來表示空指針的,但我經過一些嘗試,感覺兩者在初始化指針時的效果沒有區別,那麼為什麼要引入nullptr呢?誠心求教大家。。十分感謝~~


#include &
#include &
using namespace std;
class Object {
public:
Object(int) { cout &<&< "int"; } Object(int*) { cout &<&< "int*"; } }; int main() { //shared_ptr& pObj1 = make_shared&(0); //這裡就混淆了
auto pObj1 = make_shared&(nullptr); //int*
auto pObj2 = make_shared&(0); //int
}

因為C++禁用了void*隱式轉換向其他類型指針,所以NULL的宏實際上就是0,這樣會導致在C++11提出的完美轉發語義下,0被模板推斷成int而不是其它的什麼指針。

以及其他人所說的沒法區分到底是int 0還是指針0


http://www.stroustrup.com/C++11FAQ.html#nullptr

我可能對故事比較感興趣。。所以就去這裡查了。關於空指針用nullptr來表示。。他們討論了好久結果。。

總的來說是因為:我們不喜歡不確定的,模糊的東西,計算機更不喜歡。

C++裡面有一個特殊的規則:0既是一個常量整數,又是一個常量空指針

這導致了很多問題,特別是以下兩點:

1&>辨別null和0:舉了3個例子。。

比如重載函數的時候。。

還有就是,比如這個函數:f(char*),如果你想表達:傳一個空指針做參數,你都必須顯式地寫成f((char*)0)這種醜陋的寫法。。

還有就是,他舉的初始化字元串的例子。。我也是第一次見。。

2&>對空指針命名:

這個還比較喜劇。。他說,程序員經常請求說:給空指針一個名字吧,不要僅僅叫它:hi,小零。。這也是定義了個宏NULL來表示空指針的原因之一,雖然這種做法也不太好。

他們希望空指針有一個type-safe的名字(其實現在我也還沒體會到什麼叫type-safe),這樣的話,他說,不僅可以順帶解決前面說的問題,而且還對檢測錯誤有幫助(這個我沒有經驗。。沒有遇到過)。

這幾段裡面,作者可能假設了讀者了解NULL,所以他沒說一些關於NULL的東西。。關於NULL的話,Bjarne Stroustrup在The C++ programming language Fourth edition的7.2.2 nullptr有這麼一段:

就是說,NULL也不是一個確定的東西,NULL 是 implementation defined,可能是0也可能是0L,C語言裡面,一般NULL 是 (void*)0,但也沒說不可能是0。

然後接下來他們就提出解決方案了:為了避免這些問題,0就是0,就是一個整數,而不是其它啥東西,所以,我們需要一個不同的名字來表示空指針。

這裡我的想法是:如果NULL定義為0,然後呢,你確實可以寫int* p=NULL;但是這實際上就是把0當成空指針了,但是我們只想0表示整數;要麼,NULL定義為(void*)0,雖然這樣在C語言裡面可以,但是就像他上面舉的例子:

C++中,這樣很合邏輯的做法卻是不對的。。

然後把遇到的問題分了下類,其實我覺得主要還是避免有歧義的代碼,比如重載。。然後就是generic programming;C++能更好教,更好學?避免尷尬,困境?。。這些我都沒有經驗,沒有遇到過,還不能理解。。

接下來就是具體的怎麼用nullptr表示空指針了。

具體的細節我看不懂,不過可以看出他們還是想了好多辦法,

比如把nullptr寫成一個類:

然後就是接受(void*)0:

。。然後他說,因為(void*)0寫起來太丑了,我覺得,就是說,你也不可能,要表達空指針的時候,到處都寫個(void*)0,然後你可能還是要用那個宏NULL來命名空指針。(還是那個例子。。int* p=NULL;)

第二條中的unique semantics我也不太清楚,意思好像是說,這樣一來(void*)0就不能是void*類型(這樣的話,想起來也不太合邏輯),然後好像說了C語言類型方面的一個缺點。。

然後這個應該就是最後接受了的提議吧。nullptr成了C++的一個關鍵字。具體怎麼弄的我也就看不懂了。這份文檔是2007年的,想起來還是覺得,看起來是一點點小改變,其實還是不是那麼容易。

他們的心路歷程大概就是這樣吧。。可能讀原文要好一些,我根據自己理解翻譯的。下面是一些我自己的看法:

確實是,我們不喜歡不確定的東西,比如NULL是不確定的,0也是不確定的,但是我們知道計算機只能處理確定的東西,如果有不確定的因素,就有可能出問題。

實際上,說起來似乎也很簡單,關鍵是看我們需要什麼。

我們只是需要一個概念來表達空指針(null pointer):a pointer that does not point to an object

我們不想藉助0,一個整數來表達這個概念。

也不想藉助void*:a pointer to an object of unknown type

也就是說:

number zero : 0

a pointer to an object of unknown type : void*

a pointer that does not point to an object : nullptr

最後用nullptr來表達這個概念似乎是順水推舟,但正如大家所見,實際上背後有很多努力和思考。

還有就是關於,用了nullptr是不是更好教,更好學C++。。反正讀起那句話時,我感到一絲溫暖。。


NULL會被模板推斷為int,但nullptr就可以完美轉發至T*


nullptr只是一個區分0和NULL區別的東西,但是大多數人don"t give a shit,因為他們判斷空指針全都是if (!ptr)而不是if(ptr != NULL)。

而且你知道類型用static_cast&(NULL)一轉就好了,nullptr只是剩了這麼多位元組而已。


因為重載函數處理 NULL 的時候會出問題

void foo(int); //(1)
void foo(void*); //(2)

foo(NULL); // 重載決議選擇 (1),但調用者希望是 (2)

有趣的是 std::nullptr_t 的定義

typedef decltype(nullptr) nullptr_t; // 通過字面量反過來定義類型


因為C++他爹早就看0這個magic number不爽了,當年沒顧上搞,一直拖到2011年了,再不瘋狂一把C++就沒人用了。


當初c++迷信類型轉換不安全,禁止了void指針轉成其他指針。實際上呢,這種代碼除非你寫完不debug不運行測試拿到手直接過,否則不可能出問題。c語言沒限制,一堆腳本語言構築了龐大的系統,也沒聽說哪個因為類型轉換導致估量的損失。

因此,這個限制唯一的結果就是,#define NULL void*0編譯不過去了。這也就杜絕了c++把NULL和零區分開的可能。

用腳趾頭想也知道,整數轉化成指針比void*轉化指針更加不安全。當然,編譯器也是禁止整數轉成指針的。怎麼辦?給0做特殊處理。沿襲了c的做法。我不知道當初c++創始人那個禿子哪根筋搭錯了選了這種解決方案。

上面有人說了,0是無類型的,可以看作各種類型通用的0值。但這是不對的,0在int里是實實在在的有意義的值,而null之於對象則不是,它是無意義的,否則就會出現人和桌子的交集是null。因此null_ptr的類型也不屬於任何一種對象指針,只是可以直接轉化罷了。經過20年,c++終於把自己造的坑填上,回到「正常」的範圍。


問題的關鍵是c++不知道怎麼正確做void * 到 T *的默認轉換,所以多此一舉的引入了nullptr。

某些情況做下c++編譯器不知道怎麼把(void *)ptr正確映射到某個class ptr上,這個可以理解,但是編譯器完全可以考慮NULL這個特殊情況,NULL的情況下只需要compiler做初始化指令而跳過類型轉換動作. nullptr才是真正醜陋的東西,應該再接著搞個nullref,然後nullrefptr, nullptrref, nullptrptrptrptr....


推薦閱讀:

為什麼判斷 std::vector 是否為空時,用 if(0==vec.size()) 提示效率低,但用 if (vec.empty()) 正常?
C/C++中char/int/long等基本內置類型為何要編譯器相關而不是固定長度?
Visual C++.NET的存在意義是什麼?
如何評價 JetBrains 的新 C/C++ IDE CLion?
為什麼不能在 std::map 中使用局部類型?

TAG:C | CC | 指針 | CPrimer |