上周Meeting C++2013結束後,我對C++思考了很多,有一些內容和指針有關。在C++ 11中只對指針進行了小量的更新引入了nullptr),不過過去幾年中,C++中指針的語義和用法卻發生了很多變化。
首先,我們從指針的原始意義開始,C++11中簡單如type* pt = nullptr; 這裡的指針是C語言中的核心概念,由於C++沒有重新設計指針,據我所知C也沒用更新這部分語義。但是C規范中定義了指針,並給出了在C和C++中使用指 針的指導。事實上,指針是一個指向內存中存儲某個變量的地址。如果你對指針進行解引用操作,就能訪問指針指向的變量。指針實際上是一個基礎變量,它不知道 它所指向的值是否有效,也不能感知其指向的值是否無效。在C語言中,一個指針指向0,說明其不指向任何值,因此也不具有有個有效的值。所有其他指針都應該 指向內存中有意義的地址。但實際上,有些指針沒有正確的初始化,或者干脆越出了應有的范圍。
在C++11中,將指針正確初始化為0的方法是使用關鍵字nullptr。這讓計算機知道該指針當前為空。另外,還有一種常用的方式是將0定義為 NULL或者其他定義或聲明。C++11中使用nullptr統一了這種方式。C++中還引入了引用,它看起來像是變量的別名,其優勢是使用引用的時候必 須先初始化,因此,在引用生命周期起始時需要指向一個有效地址。不過,引用也只是指針的解引用,所以,一旦其引用的變量作用范圍結束,其引用也無效了,使 用指針時,你可以將指針置為0,但是針對引用卻不能這麼做。
但是在C++11和在C++11標准之前,一些事情發生了變化,指針是語言的核心概念,但是你在現代化的C++代碼和函數庫中卻很少看到它們。遠在 C++11之前,boost創建了一系列非常有用的智能指針類,針對指針進行了封裝,對其核心機制通過操作符重載。智能指針本身不是一個指針,而是一個棧 上的變量或對象成員。智能指針使用了RAII來解決指針的一些問題,這並不是指針的職責。當在椎中分配內存時,new返回了指向該部分內存的地址,所以每 分配一塊動態內存,就需要使用一個指針,相當於創建對象的一個操作句柄。但是指針僅僅是一個簡單的變量,不知道變量的擁有關系,也不能自動釋放堆上的內存 空間。智能指針擔當了這一角色,擁有指針並在變量超出作用域時自動管理其堆上的值。在棧上的值意味著,一旦相應的棧被銷毀,其管理的堆上的值會被自動釋 放,即使是在發生異常的情況下。
過去的一些年,C++出現了一些不同風格的使用,從使用類的C及大量使用指針,到類似我想Widget和QT這樣面向對象的框架。在過去5-10年 中的形成的一種新樣式被認為是現代C++,一種趨向盡力發掘語言本身擴展能力,並試圖找到不同特性針對不同場合的應用。值得注意的是boost在這一趨勢 中起到了引領風范的C++框架。C++標准在設計其標准庫時也借鑒了這一點。與此同時,值語義變得流行起來,並且與move語義成為未來C++一個關鍵點。來自Tony van Eerds在Meeting C++的一份備忘幻燈片引起了我對指針的思考。它有兩列,一個代表引用語義,一個代表值語義,以及其朗朗上口的主題詞:
哦,不!使用指針 vs 哦,不要使用指針!
所以,在C++11或者後續的C++14,使用值語義的趨勢蓋過了使用指針。指針在取後台還是工作著,不過在新的C++14中,new和 delete都將不能直接使用,new被抽象化為make_shared/make_unique。其內部使用了new,但是返回一個智能指針。 shared_ptr 和 unique_ptr都表現為值語義類型。智能指針同樣在其作用域結束時使用delete釋放內存。這讓我思考,C++中的指針是不是都可以填充不同的 “角色”,或者被替換掉。
繼承和虛擬函數
指針一個非常重要的用途是在繼承中使用指針來指向一系列擁有相同接口的類型值。我想用Shape例子來闡明這一點,這裡有一個基類Shape,同時 其含有一個虛擬函數叫area的方法。同時,它還有幾個派生類叫Rectange,Cirecle和Triangle。現在,有一個指針容器比 如:std::vector<Shape*>)來容納指向不同形狀的對象指針,每個對象都有自己的計算面積方法。這是C++中最常用指針的方 式,尤其是在面向對象時。現在,好消息是,這裡同樣支持使用智能指針,當其使用這些智能指針時,內部會進行訪問指針。Boost中甚至還有一個指針容器, 能在清空容器時自動釋放其中的智能指針元素。
現在考慮虛函數調用這雖然不和指針有直接聯系),虛函數調用通常會有點點慢,同時也不容易編譯器針對其進行優化。所以,如果其類型在運行時是可知 的,就可以使用靜態分發或者編譯器多態性來正確調用相應的虛函數方法,而不是在運行時使用虛函數指針。作為一種模式被叫做CRTP,已經實現了這一方式。 最近的研究顯示,這在gcc4.8中可以提高性能。有趣的是,通常情況下使用gcc4.9,優化器可以針對動態分發進行更進一步的優化。還是讓我們繼續回到指針。
不確定指針
有時候指針被用於有一系列可選值作為參數或者返回不確定的函數中,通常都默認為0,用戶可以選擇傳遞一個有效的指針給該函數。或者在返回的情況下, 函數返回一個空指針表示執行失敗,這在C++中也是一個有效的使用方式。同樣的,這裡可以使用智能指針,智能指針可以扮演指針的操作句柄。不過常常會導致 過量使用使用堆),或者並沒有替代不確定的角色。這需要使用一個可選值類型來代替,用於確定其存儲的值是否有效。Boost庫有一個boost::optional來表示可選值類型。因此,可以考慮在C++14中引入有一個類似的可選類型。所以,現在std::optional會被移入到技術預覽版TS)中,將來會變成C++14或者C++1y的一部分。
當前的標准庫中已經使用了一些可選類型,比如std::set::insert會返回一個pair<iterator,bool>類 型,其第二個參數表示請求值是否插入到set容器中。容器通常返回尾迭代器來表示無效,但是如果要求返還一個值時,這個角色過去通常都是用指針來表示,指 針為0表示函數執行失敗,因此這裡的指針可以被可選類型替代:
- optional<MyValue> ov = queryValue(42);
- if(ov)
- cout << *ov;
- else
- cerr << "value could not be retrieved";