Item 6: 當auto推導出一個不想要的類型時,使用顯式類型初始化的語法

本文翻譯自《effective modern C++》,由於水平有限,故無法保證翻譯完全正確,歡迎指出錯誤。謝謝!

博客已經遷移到這裏啦

Item 5解釋了比起顯式指定類型,使用auto來聲明變量提供了大量技術上的優點,但是有時候auto的類型推導出zigs(這個類型),但是你想要的是zag(另外一個類型)。舉個例子,假設我有一個函數以Widget爲參數並且返回一個std::vector,每個bool指示Widget是否提供了特定的特性:

std::vector<bool> features(const Widget& w);

進一步假設bit 5指示了Widget有高優先級。因此我們寫了如下的代碼:

Widget w;
...
bool highPriority = features(w)[5];
...
processWidget(w, highPriority); //根據w的優先級處理w

這段代碼沒有錯。它工作得很好。但是如果我們做一些看起來無害的操作,也就是用auto來替換highPriority的顯式類型聲明,

auto highPriority = features(w)[5];

情況改變了。所有的代碼將能繼續通過編譯,但是它的行爲不再和預測的一樣了:

processWidget(w, highPriority); //未定義的行爲!

就像註釋指示的那樣,調用processWidget現在是未定義的行爲。但是爲什麼會這樣?回答有可能很奇怪。在使用auto的代碼中,highPriority的類型不再是bool、儘管std::vector概念上保存了bools,std::vector的operator[]不返回容器元素的引用(除了bool類型,其他的std::vector::operator[]都這麼返回)。作爲替換,它返回一個類型是std::vector::reference的對象(一個類內部裝了std::vector)。

std::vector::reference之所以會存在是因爲std::vector被指定來代表它包裝的bools,每個bool佔一bit。這將造成一個問題,對於std::vector的operator[],因爲對於std::vector,operator[]被假定要返回T&,但是C++禁止返回bits的引用。因爲沒有可能返回一個bool&,operator[]爲std::vector返回一個對象,這個對象表現得像bool&一樣。爲了這個行爲能成功,在本質上,std::vector::reference對象必須能被用在任何bool&能用的地方。在C++的衆多特性中,能讓std::vector::reference這麼工作的是一個到bool的隱式轉換。(不是bool&到bool,對於std::vector::reference如何效仿bool&,如果要解釋清楚所有的技術那就走遠了,所以我只是簡單地談論了衆多技術中的一個,隱式轉換。)

記住了這些信息後,再看看源代碼的這一部分:

bool highPriority = features(w)[5]; //顯式聲明
                                    //highPriority的類型

這裏,features返回一個std::vector對象,它的operator[]被調用。operator[]返回一個std::vector::reference對象,根據highPriority初始化的需要,這個對象將會隱式地轉換爲bool。因此highPriority最後代表features返回的std::vector中的bit 5的值(就像它支持的那樣)。

對比對highPriority使用auto初始化聲明時發生的情況:

auto highPriority = features(w)[5]; //推導highPriority的類型

再一次,features返回一個std::vector對象,並且,再一次,operator[]被調用。operator[]繼續返回一個std::vector::reference對象,但是現在這裏有些變化,因爲auto推導highPriority的類型爲std::vector::reference。highPriority不再擁有features返回的std::vector中的bit 5的值。

它擁有的值依賴於std::vector::reference是怎麼實現的。一種實現是,這些對象包含一個指針指向一個機器字節(word)的引用,以及一個代表特定bit的偏移。考慮一下,這樣一個std::vector::reference的實現,對於highPriority的初始化意味着什麼。

對於features的調用返回一個std::vector臨時對象。這個對象沒有名字,但是爲了討論的目的,我講把它叫做temp。operator[]是對temp的調用,並且std::vector::reference返回一個對象,這個對象指向一個字節(word),這個被temp管理的數據結構中的字節持有所需的bit,加上對象存儲的偏移就能找到bit 5。highPriority是這個std::vector::reference對象的一份拷貝。所以highPriority也持有指向temp中某個字節(word)的指針,以及bit 5的偏移。在語句的最後,temp銷燬了,因爲它是一個臨時對象。因此,highPriority持有的是一個懸掛的指針,並且這就是在processWidget調用中造成未定義行爲的原因:

processWidget(w, highPriority); //未定義行爲!
                                //highPriority持有懸掛的指針

std::vector::reference是一個代理類的例子:一個類的存在是爲了效仿和增加其他類型的行爲。代理類被用作各種各樣的目的。std::vector::reference爲了提供一個錯覺:std::vector的operator[]返回一個bool引用。再舉個例子,標準庫的智能指針是一個對原始指針進行資源管理的代理類。代理類的用法已經慢慢固定下來了。事實上,設計模式“Proxy”就是軟件設計模式萬神殿(Pantheon)中長期存在的一員。

對於客戶來說,一些代理類被設計成可見的。舉個例子,這就是std::shared_ptr和std::unique_ptr的情況。另外一種代理類被設計成或多或少不可見的。std::vector::reference就是這種“不可見”代理類的例子,對於它的同胞std::bitset也有這樣的代理類:std::bitset::reference。

同樣在這個陣營中,一些C++庫中的類使用一種叫expression template的熟知科技。這樣的庫最初開發出來是爲了提升算術運算代碼的效率。給出一個類Matrix和Matrix對象m1,m2,m3和m4,舉個例子,表達式

Matrix sum = m1 + m2 + m3 + m4;

能被計算得更加有效率一些,只需要讓Matrix對象的operator +返回一個結果的代理來代替結果本身。也就是,兩個Matrix對象的operator +將返回一個對象,這個對象用像Sum

            你要記住的事
  • 對於一個初始化表達式,“看不見的”代理類型能造成auto推導出“錯誤的”類型
  • 顯式類型初始化語法強制auto推導出你想要它推導的類型。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章