Effective Modern C++ 學(xué)習(xí)筆記1——類型推導(dǎo)

類型推導(dǎo)規(guī)則

在大多數(shù)情況下,模板與auto的類型推導(dǎo)規(guī)則一致,且規(guī)則很簡(jiǎn)單廊遍。

情況1. 沒有加任何修飾

// 模板函數(shù):
template <typename T>
f(T t) {...}

// auto聲明:
auto x = ...;

這種情況下,參數(shù)是按值傳遞贩挣,形參t或者變量x都是一個(gè)副本喉前,那么就需要去掉引用没酣,且副本本身沒必要加cv限定符(const、volatile卵迂,下文都不考慮volatile裕便,只說const),也需要去掉见咒。

const int& a = ...;

f(a);       // T 推導(dǎo)為 int
auto x = a; // x 的類型為 int

但需要注意闪金,只能去掉變量本身的const,對(duì)于指針類型论颅,所指對(duì)象的const修飾不能去掉哎垦。

const int* const a = ...;

f(a)        // T 推導(dǎo)為 const int*
auto x = a; // x 的類型為 const int*

其實(shí)不需要特別記憶,只要記住一點(diǎn):在能編譯通過的前提下恃疯,去掉沒必要的const和&符號(hào)即可:

const int& a = ...;

// 以下語句能否編譯通過漏设?
const int& x = a;       // OK
int& x = a;             // 編譯不通過,const int& 類型不可轉(zhuǎn)為int&
const int x = a;        // OK
int x = a;              // OK今妄,且去掉了多余的const和&符號(hào)
// 因此郑口,auto x = a; 中,x類型推導(dǎo)為 int

const int* const b = ...;

// 以下語句能否編譯通過盾鳞?
int* const x = b;       // 編譯不通過
const int* const x = b; // OK
const int* x = b;       // OK犬性,且去除了多余的const
// 因此,auto x = b; 中腾仅,x類型推導(dǎo)為 const int*

情況2. 加了引用

也就是T&的形式:

// 模板函數(shù):
template <typename T>
f(T& t) {...}

// auto聲明:
auto& x = ...;

這種情況則需要保持原有的const乒裆,舉例來看:

const int a = 1;

f(a); // T 推導(dǎo)為 const int,所以形參t類型為 const int&
auto& x = a; // x類型推導(dǎo)為 const int

保留const的原因也很好理解推励,因?yàn)檫@里是引用傳遞鹤耍,丟掉const會(huì)導(dǎo)致可以修改原來的值,同樣編譯不通過:

const int a = 1;
int& x = a; // 編譯不通過
const int& x = a; // OK

情況2.2 萬能引用

如果加了兩個(gè)引用符號(hào) &&验辞,則可以根據(jù)情況推導(dǎo)為普通的引用稿黄,或者右值引用,因此也叫做萬能引用:

// 模板函數(shù):
template <typename T>
f(T&& t) {...}   // &&表示萬能引用

// auto聲明:
auto&& x = ...;  // &&表示萬能引用

// 舉例:
int a = 1;
auto&& x = a;     // x的類型是 int&
auto&& x = 1;     // x的類型是 int&& 右值引用

其他情況

經(jīng)過上面的分析跌造,我們得出結(jié)論杆怕,對(duì)于模板類型的推導(dǎo),其實(shí)只要能替換成普通函數(shù)壳贪,并去掉不必要的const和引用&陵珍,就可以得到推導(dǎo)結(jié)果。

書中特別提到了數(shù)組作為函數(shù)實(shí)參的特殊情況撑碴,但實(shí)際上這并不算什么特殊情況撑教。我們先了解下普通函數(shù),數(shù)組作為參數(shù)的情況醉拓。

在普通函數(shù)簽名中伟姐,形參可以寫成數(shù)組的形式收苏,但實(shí)際上類型還是指針。二者最大的區(qū)別是:指針并不會(huì)包含數(shù)組長(zhǎng)度信息愤兵。這也是為什么函數(shù)以數(shù)組作為參數(shù)時(shí)鹿霸,一般還要再傳入數(shù)組長(zhǎng)度參數(shù)。

int getlen(char a[]) { // 等價(jià)于 int* a 或 int a[1]
    return sizeof(a);
}

int main() {
    char a[2];
    cout << sizeof(a); // 輸出 2 即兩個(gè)char所占的空間
    cout << getlen(a); // 輸出 8 即int*指針?biāo)嫉目臻g
}

其實(shí)秆乳,如果想要保留數(shù)組的長(zhǎng)度信息懦鼠,也有辦法,那就是使用“數(shù)組的引用”作為函數(shù)參數(shù):

int getlen(char (&a)[2]) { // a是數(shù)組char[2] 的引用類型
    return sizeof(a);  // 返回2
}

注意屹堰,形參中需指定長(zhǎng)度肛冶,且需要與實(shí)參的數(shù)組長(zhǎng)度一致。

而對(duì)于模板函數(shù)其實(shí)是一樣的扯键,可以使用數(shù)組作為參數(shù)睦袖,這樣實(shí)際上的類型是指針;也可以使用“數(shù)組的引用”作為參數(shù)荣刑,保留長(zhǎng)度信息馅笙。

template<typename T>
int f(T t) {
  return sizeof (t);
}

template<typename T>
int g(T& t) {
    return sizeof(t);
}

char a[2];
cout << f(a) << endl; // 輸出 8, 即指針的長(zhǎng)度,t被推導(dǎo)為char*類型
cout << g(a) << endl; // 輸出 2, t被推導(dǎo)為 char(&)[2] 類型
auto x = a;   // x類型推導(dǎo)為 char*
auto& y = a;  // y類型推導(dǎo)為 char(&)[2]

仔細(xì)對(duì)比后發(fā)現(xiàn)厉亏,其實(shí)這和普通函數(shù)的規(guī)則完全一致董习,即:數(shù)組會(huì)退化為指針,數(shù)組引用則不會(huì)爱只。

有趣的是皿淋,利用這一特性,我們可以寫一個(gè)函數(shù)虱颗,獲得數(shù)據(jù)長(zhǎng)度:

template<typename T, std::size_t N>
constexpr std::size_t arraySize(T(&)[N]) {
  return N;
}

char a[2];
// 聲明一個(gè)與數(shù)組a長(zhǎng)度相同的array
std::array<char, arraySize(a)> arr{};

arraySize聲明中的constexpr表示:返回的N是編譯期常量沥匈。這樣才可以用在array的聲明中蔗喂。

auto與模板唯一的區(qū)別

前面的每個(gè)例子中忘渔,都同時(shí)包含了模板與auto的類型推導(dǎo),它們二者的推導(dǎo)結(jié)果都一摸一樣缰儿。二者唯一的一點(diǎn)不同是:auto支持初始化表達(dá)式畦粮,而模板不支持:

template<typename T>
void f(T param);

f({1,2,3});          // 錯(cuò)誤,無法推導(dǎo)
auto x = {1,2,3};    // OK乖阵,x類型推導(dǎo)為std::initializer_list<int>
auto y = {1,2,3.0};  // 錯(cuò)誤宣赔,包含int和double,無法推導(dǎo)
int[] z = {1,2,3};   // OK

在上例中瞪浸,模板與auto出現(xiàn)區(qū)別的根本原因在于儒将,C++11中為了支持統(tǒng)一初始化,可以用這種語法進(jìn)行聲明对蒲;而函數(shù)參數(shù)則沒有這樣的語法钩蚊。

當(dāng)然贡翘,這樣推導(dǎo)的結(jié)果總是std::initializer_list<T>的類型,往往不是我們想要的結(jié)果砰逻,所以還是盡量去避免這樣使用鸣驱。

auto使用的優(yōu)劣

大多數(shù)時(shí)候,優(yōu)先使用auto

使用auto的好處包括:

  • 簡(jiǎn)化一長(zhǎng)串復(fù)雜的類型
  • 避免忘記初始化(使用auto未初始化會(huì)產(chǎn)生編譯錯(cuò)誤)
  • 類型變更時(shí)可以自適應(yīng)
  • 能夠?yàn)閘ambda表達(dá)式聲明變量蝠咆,且優(yōu)于std::function踊东,不需要堆內(nèi)存
  • 避免由于類型寫錯(cuò)導(dǎo)致額外的運(yùn)行開銷

關(guān)于最后一點(diǎn),額外的運(yùn)行開銷刚操,舉例說明一下:

std::unordered_map<std::string, int> m;

// 不使用auto進(jìn)行遍歷闸翅,代碼出現(xiàn)瑕疵:
for (const std::pair<std::string, int>& p : m) {
    //... 對(duì)p進(jìn)行操作
}

// 使用auto:
for (const auto& p : m) {
    // ...
}

在for循環(huán)中,我們本意并不希望發(fā)生任何拷貝菊霜,因此使用了const引用的形式聲明p缎脾,但很可惜,在代碼運(yùn)行中占卧,仍然會(huì)發(fā)生拷貝遗菠。為什么呢?

原因是华蜒,m中元素正確類型是:std::pair<const std::string, int>辙纬,const不可以丟掉。由于這和代碼中p的類型不匹配叭喜,編譯器就會(huì)將其拷貝一份贺拣,并進(jìn)行類型轉(zhuǎn)換。所以捂蕴,由于程序員對(duì)p類型的判斷失誤譬涡,導(dǎo)致運(yùn)行期額外的性能開銷。

而使用auto時(shí)啥辨,則永遠(yuǎn)會(huì)推斷出正確的類型涡匀,可以完全避免這個(gè)問題。

auto使用的坑

有一種情況溉知,使用auto無法得到我們想要的類型陨瘩,這甚至可能會(huì)導(dǎo)致十分隱蔽的問題。例如:

std::vector<bool> GetVec() {
  return {true};
}

void HandleBool(bool b) {
  cout << b;
}

int main() {
  auto b = GetVec()[0]; // 這里auto如果換成bool级乍,一切正常
  HandleBool(b);
}

本例中舌劳,我們將std::vector<bool>的operator[]操作結(jié)果賦值給b,期望變量b是bool類型玫荣,并在HandlerBool中進(jìn)行打印甚淡。然而實(shí)際運(yùn)行發(fā)現(xiàn),打印的值往往不符合預(yù)期捅厂。

這是為什么贯卦?原因在于底挫,b的類型并沒有推斷為bool,而是一個(gè)std::vector<bool>::reference類型的復(fù)雜對(duì)象脸侥,其中包含一個(gè)指針建邓,指向vector<bool>中的某個(gè)位置。

之所以這樣做睁枕,是因?yàn)関ector內(nèi)部針對(duì)bool類型做了一些優(yōu)化官边。正常情況下,這個(gè)對(duì)象可以正確轉(zhuǎn)化成bool類型外遇。但是注簿,在上例中,GetVec返回的vector在執(zhí)行完這一行代碼后已經(jīng)銷毀跳仿,b對(duì)象中的指針則成為野指針诡渴,在調(diào)用HandlerBool時(shí),無法正確轉(zhuǎn)化成bool類型菲语。

在本例中妄辩,std::vector<bool>::reference類型我們稱之為代理類,它可以模擬另一種類型山上。類似的眼耀,智能指針shared_ptr、unique_ptr也是代理類佩憾,它們可以模擬普通指針哮伟。但本例中的代理類更加隱蔽,我們難以察覺妄帘,才會(huì)出現(xiàn)上述問題楞黄。

所以,對(duì)于這種隱形代理類抡驼,需要根據(jù)情況避免使用auto來聲明鬼廓。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市婶恼,隨后出現(xiàn)的幾起案子桑阶,更是在濱河造成了極大的恐慌,老刑警劉巖勾邦,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異割择,居然都是意外死亡眷篇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門荔泳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕉饼,“玉大人虐杯,你說我怎么就攤上這事∶粮郏” “怎么了擎椰?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)创肥。 經(jīng)常有香客問我达舒,道長(zhǎng),這世上最難降的妖魔是什么叹侄? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任巩搏,我火速辦了婚禮,結(jié)果婚禮上趾代,老公的妹妹穿的比我還像新娘贯底。我一直安慰自己,他們只是感情好撒强,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布禽捆。 她就那樣靜靜地躺著,像睡著了一般飘哨。 火紅的嫁衣襯著肌膚如雪睦擂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天杖玲,我揣著相機(jī)與錄音顿仇,去河邊找鬼。 笑死摆马,一個(gè)胖子當(dāng)著我的面吹牛臼闻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播囤采,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼述呐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蕉毯?” 一聲冷哼從身側(cè)響起乓搬,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎代虾,沒想到半個(gè)月后进肯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡棉磨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年江掩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡环形,死狀恐怖策泣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抬吟,我是刑警寧澤萨咕,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站火本,受9級(jí)特大地震影響危队,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜发侵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一交掏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刃鳄,春花似錦盅弛、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至愉烙,卻和暖如春讨盒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背步责。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工返顺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔓肯。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓遂鹊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蔗包。 傳聞我的和親對(duì)象是個(gè)殘疾皇子秉扑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容