在師姐的建議下,買了《Effective C++》這本書,打算用一個月的零散時間看完,并用這個專欄來記錄我的學習心得。
導讀
#1
關鍵字 explicit 用來阻止構造函數的隐式轉換。
被聲明為explicit的構造函數通常比其non-explicit兄弟更受歡迎。因為它們禁止編譯器執行非預期(往往也不被期望)的類型轉換。除非我有一個好理由允許構造函數被用于隐式類型轉換,否則我會把它聲明為explicit。我鼓勵你遵循相同的政策。(部落客注:這裡說類型轉換容易讓人誤解,所謂轉換并不是我們了解的“類型轉換”,而是調用構造函數生成對象,是以說 explicit 禁止編譯器隐式調用構造函數,必須顯式調用構造函數)
該關鍵字常用在類的構造函數上。讓我們用例子來說明:
#include <iostream>
using namespace std;
class boy
{
public:
int age;
boy(int _age):age(_age){}
};
class girl
{
public:
int age;
explicit girl(int _age):age(_age){} //使用 explicit 阻止隐式轉換
};
int main()
{
boy b1(1); // 顯式調用構造函數
boy b2 = 2; // “隐式轉換”,即隐式調用了構造函數,相當于 b2(2)
girl g1(1); // 顯式調用構造函數
girl g2 = 2; // 禁止隐式調用,編譯報錯,error: conversion from ‘int’ to non-scalar type ‘girl’
return 0;
}
同時我們還需注意的是,當構造函數參數超過兩個時自動取消隐式調用構造函數,但有例外:構造函數有預設值,或者說是除了第一個參數,都有預設值。(注意:C++11标準重新定義了)
例如:
#include <iostream>
using namespace std;
class boy
{
public:
int age;
int height;
boy(int _age, int _height):age(_age), height(_height){}
};
int main()
{
boy b = 2;
return 0;
}
編譯報錯。如果改成:
#include <iostream>
using namespace std;
class boy
{
public:
int age;
int height;
boy(int _age = 0, int _height = 50):age(_age), height(_height){}
};
int main()
{
boy b = 2;
return 0;
}
此時就會有隐式調用,那麼唯一的參數就會賦給 age 。
一次意外,我使用了 explicit 修飾了拷貝構造函數,又學習到了。explict 還能夠修飾拷貝構造函數。參考:【C++】explicit關鍵字(一點心青)
#include <iostream>
using namespace std;
class boy
{
public:
explicit boy(int age, int height):age(age), height(height) {}
explicit boy(const boy& b):age(b.age), height(b.height) {}
private:
int age;
int height;
};
boy CopyBoy(const boy& b) {
return boy(b); // 傳回值需要隐式調用拷貝構造,編譯報錯
}
int main()
{
boy b1(10, 160);
boy b2 = b1; // 隐式調用拷貝構造,編譯報錯,a.cc:18:11: error: no matching function for call to ‘boy::boy(boy&)’ boy b2 = b1;
boy b3(b1); // 顯式調用拷貝構造,通過編譯
boy b4 = CopyBoy(b1); // 隐式調用拷貝構造,編譯報錯
return 0;
}
将拷貝構造函數聲明為explicit,則會阻止隐式拷貝構造函數的調用。拷貝構造函數被隐式調用主要發生在三處:
1.一個對象作為函數參數,以值傳遞的方式傳入函數體.
2.一個對象作為函數傳回值,以值傳遞的方式從函數傳回.
3.以AAA = xxx的方式建立對象AAA,xxx為與AAA為同類型的對象.
因而,将拷貝構造函數聲明成explicit并不常見,一般隻将 constructor 聲明為explicit,而copy constructor不要聲明為explicit。
說到這裡我們沒有看到隐式轉換有什麼壞處啊,那麼可以參考這裡,explicit 可以有效防止構造函數的隐式轉換帶來的錯誤或者誤解
Google認為:代碼的行為應該是明确的,不能偷偷摸摸的做一些事情給查找問題帶來困難。
https://github.com/lichuang/codedumpnote/blob/master/20160410-cpp-learn/20160410-cpp-learn.md
http://www.cnblogs.com/cutepig/archive/2009/01/14/1375917.html
Upate:C++11
C++11之前,沒有花括号初始化方法,當構造函數擁有多于1個的參數沒有預設值時,隐式調用将會被自動禁用。然而C++11引入了花括号清單初始化,使用 explicit 關鍵字來區分是否能夠隐式調用構造函數,并且與預設參數的個數無關。
可以參見
explicit specifier
Converting constructor
舉例如下
#include <iostream>
using namespace std;
class boy
{
public:
boy(int age, int height):age(age), height(height) {}
boy(const boy& b):age(b.age), height(b.height) {}
private:
int age;
int height;
};
int main()
{
boy b1 = {10, 11}; // 隐式調用構造函數
boy b2{10, 11}; // 顯式調用構造函數
return 0;
}
編譯通過。
#include <iostream>
using namespace std;
class boy
{
public:
explicit boy(int age, int height):age(age), height(height) {}
boy(const boy& b):age(b.age), height(b.height) {}
private:
int age;
int height;
};
int main()
{
boy b1 = {10, 11};
boy b2{10, 11};
return 0;
}
隐式調用報錯:
error: converting to ‘boy’ from initializer list would use explicit constructor ‘boy::boy(int, int)’
boy b1 = {10, 11};
#2
"=" 等号即可以用作指派函數,也可用作拷貝構造函數。
幸運的是 “copy構造” 很容易和 “copy指派” 有所差別。如果一個新對象被定義,一定會有個構造函數被調用,不可能是指派操作。如果沒有新對象被定義,就不會有構造函數被調用,那麼當然就是指派操作被調用。
舉個例子:
#include <iostream>
using namespace std;
class boy
{
public:
int age;
int height;
boy(int _age, int _height):age(_age), height(_height){}
boy(const boy &b){age = 19; height = 170;}
boy& operator=(const boy &b){age = 21; height = 180;}
void display(){cout << age << " " << height << endl;}
};
int main()
{
boy b1(20, 175);
boy b2 = b1; //調用拷貝構造函數
b2.display();
b2 = b1; //調用指派函數
b2.display();
return 0;
}
完成導讀的内容,我們以條款1結束這節的内容。
條款01:視C++為一個語言聯邦
為更好的了解C++,我們其看做四個部分,每個部分有各自的規約:
1)C
2)Object-Oriented C++
3)Template C++
這是 C++ 的泛型程式設計(generic programming)部分,也是大多數程式員經驗最少的部分。
4)STL
請記住:C++ 高效程式設計守則是狀況而變化,取決于你使用 C++ 的哪一部分。