auto類型推導


引言

auto : 類型推導. 在使用c++的時候會經常使用, 就像在考慮STL時迭代器類型, 寫模板的時候使用auto能少寫代碼, 也能幫助我們避免一些隱患的細節.

auto初始化

  1. 使用auto型別推導要求必須在定義時初始化, 畢竟需要根據對象的類型推導左值對象的型別.
auto j; 	// error. 必須初始化
auto i = 0; // i 推導型別為 int
vector<int> v; 
auto vv = v.cbegin();	// vv  推導型別為 const int*
  1. 但是auto型別推導會忽略引用和頂層const, 所以要對對象加上想要的修飾.
const int ci = 0;
auto i = ci;	// i 推導型別為 int, 忽略了頂層const
int &ri = i;
auto ii = ri; 	//ii 推導型別為 int, 忽略了引用
  1. C11之前只能通過()=對變量初始化, C++11增加了對定義的對象初始化的方法,可以使用{}對變量初始化

c11之前的初始化方法

int i(0);	// i 初始化 0
int j = 0; 	// j 初始化 0

c11后的初始化方法

auto i(0); auto j = i;	// 支持c11前
auto ii{0};	// 使用 {} 進行初始化, 但是auto推導只能接受一個參數
auto jj = { 0 };	// jj 的推導型別為 initializer_list<int>型別

上面jj的推導居然不是int型別, 而是 initializer_list<int> , 這不能怪auto推導出問題, 這主要是后者的對象初始化就是使用={}, 可以說是auto推導的是最精確的型別. 不管新添的初始化方法, 找一個習慣的就行了.

auto與for

auto最常見的就是與for聯用, 特別是類型特別復雜的時候. 但是auto又有多種選擇, 如 : auto, auto &等, 不同的選擇其效率也不一樣.

  1. auto , 即 for(auto i:range) . 這使range中的每一個元素都會產生一個副本, 所以即使修改了 i 也不會實際影響到range.
  2. const auto, 及for(const auto i : range). 這也會是range的每一個元素產生一個副本, 但是這個副本竟不能被修改.
  3. auto &, 即for(auto &i : range). 引用, 因為i 直接引用range里面的元素, 所以並不會產生一個副本, 但是 i 的修改也會影響range里元素的值. 通常我們需要修改range是會考慮用到.
  4. const auto&, 即for(const auto &&i : range). i 直接引用range里面的元素, 所以並不會產生一個副本, 並且i 也不能修改. 一般初始化的是一個左值時而且是讀取range里的元素時都是用const auto&而不用auto, 因為前者不會產生副本, 效率要高. 當然一般初始化的是一個左值時效率低, 但是如果是右值還是使用const auto效率高, 因為const auto &需要把 i 存儲在內存中的一個位置,間接訪問會更消耗時間
  5. auto&&, 即for(auto &&i : range). 如果初始化是左值, 那么 i 就是左值引用, 如果初始化是右值, 那么 i 就是右值引用,

還有const auto &, 當然具體的選擇還是看具體的情況而定.

最后, 當用auto推導多維數組的時, 保證除最內層循環外, 其他的外層循環都應該是引用類型, 否則很容易出錯, 即 :

int a[10][10][10];
for (const auto &i : a)
    for(const auto &j : i)
        for(const auto k : j)
            ;

最好使用auto型別推導

1. 初始化

  • 在定義對象的時候可能或多或少會忘記對變量進行初始化, 但當我們使用該變量的時候就會出錯, 而且問題也不好找出來, 但是使用auto定義對象就要求必須初始化有時還能減少代碼量, 上面我們已經分析過了.
  • 使用auto初始化在平台上還有一點好處, 比如 :
vector<int> v;
unsigned size = v.size();	// size()返回size_t型別
auto sizet = v.size();

​ 在32的平台unsigned代表的是32位, size_t是32位, 在64的平台unsigned代表的也是23位, 但是size_t卻是64位, 這樣平台差異可能就會帶來問題, 使用auto代替就沒有這樣的問題.

不過只有這幾點可能不會讓人心動, 下面我們還有auto的好處.

2. STL使用型別推導

還記得在前言中個說過調用STL最好使用auto推導型別, 如果你還記得mappair 嗎? 是這樣 map<pair<key, type>>? 還是map<pair<const key, type>>? 答案是最后一種, 那么現在我們就來分析的使用auto推導還是顯示型別比較好.

int main()
{
	std::map<string, std::function<type(type, type)>>func = {
		{ "+", [](auto i, auto j)->auto {return i + j; } },
		{ "-", [](auto i, auto j)->auto {return i - j; } },
		{ "*", [](auto i, auto j)->auto {return i * j; } },
		{ "/", [](auto i, auto j)->auto {return i / j; } }
	};

	for (const auto &i : func) ;

	for(const std::pair<string, std::function<type(type, type)>> &pa : func) ;

	system("pause");
	exit(EXIT_SUCCESS);
}

看到上面的例子毫無問題, 但是深究起來顯示型別還是些不完美. 我們知道map的key不能被改變, 所以顯示型別的string與map的const string不是匹配, 編譯器就會將map對象都會產生一個臨時對象再隱式的轉為string, 等等. 是不是注意到有一點了, 為了型別匹配賦值會產生臨時變量, 那豈不是每一循環都會產生一個臨時變量, 但是auto型別推導就是精確匹配的, 不會產生臨時變量.

可能覺得將顯示型別的key改為const string就能解決這個問題了, 確實是這樣, 但是如果沒有注意到這一點細節, 那就會損失效率了, 使用auto可以完全不想這些問題啊.

當然使用顯示型別還是型推導看實際也看個人, 不是必要.

auto與函數返回類型

auto不能被聲明為返回值,auto不能作為形參,auto不能被修飾為模板參數. 那么這里auto還能怎么和函數關聯起來? 能.

auto放在函數名前面告訴編譯器,真正的返回值在函數聲明之后. 簡單說auto可以作為返回值占位符來使返回值后置.

就像這樣來寫.

auto Return(std::size_t N) -> std::size_t
{
	return N;
}

既然c++規定可以這樣寫肯定有其意義. 其實這個寫法主要用於template中, 當返回值的類型是一個模板類型時使用, 而返回值類型通過decltype來推導.

這里就解釋一下decltype的簡單運用. , decltype也是類似與auto的關鍵字, 都能夠進行參數類型推導, 但是decltype必須要接受一個參數, 如下:

int i = 1;
decltype(i) j = 1;

auto與模板函數連用時用模板參數作為返回值. 因為編譯器並不能直接推斷出返回值為類型參數的實際類型, 所以在STL中采用traits編程解決這個問題, 這里時另一種實現方法.

首先看一個錯誤的例子:

template<class T1, class T2, class T3>
    T3 fun(T1 t1, T2 t2) {...}

T3的類型要在函數返回的時候才能知道, 而函數這樣寫就必須要編譯期間就要知道返回值類型. 所以編譯器會報錯.

以下這樣寫就是正確的, 但是必須保證編譯器能推導出類型.

template<class T1, class T2, class T3>
    T1 fun(T1 t1, T3 t3) {...}

使用auto將返回值類型放在最后, 就是告訴編譯器真正的返回值在編譯后動態獲取, 而auto在這里的作用也稱為返回值占位

template<class T1, class T2>
    auto fun(T1 t1, T2, t2) -> decltype(*t1) {...}

以上可以將返回類型放在函數尾做尾置是C11中的要求, 但是C14已經可以將返回型別放推導在函數頭. 如 :

template<class T1>
decltype(auto)fun() {...}	// 這樣的寫法同上式一樣

雖然規定能夠這樣寫, 有時為了兼容也還是寫成尾置.

auto與new運算符

我們可以使用auto來推斷出new對象的類型, 但是局限在於, 必須對new出來的對象進行單一的初始化.

auto i = new int; // 這中寫法根本沒有用到auto的推導哦, 因為new的類型已經確定了

auto i = new auto(1);		// 這里就是用到了auto推導
auto size = new auto;		// error, 不能推導出size的類型
auto j = new auto(1,2); 	// error, 只能接收一個初始化值

const中我們分析到頂層const會被忽略, 所以auto是無法推斷出頂層const, 即 :

auto i = new const auto(1); 	// 這里auto並沒有推導出頂層const, 所以i的類型實際上是int
const auto j = new const auto(1);	// 只有顯示的定義j的類型是const

如果想直接推導出頂層const的話, 最好還是decltype進行推導.

注意 : auto推導只能推導出int, double等, 不能推導出short類型.

總結

本節對C11的auto用法做了一個淺顯的分析, 分別對使用auto的好處, 定義時注意{}對象也必須初始化, auto在與for連用的時候要根據實際參數確定選擇哪種實現, 這樣效率才會達到最大, 當然一般都使用const auto&auto&&. 最后還對auto與函數返回值關聯, 可以將返回型別放在函數名尾也可以, 這樣的做法一般在模板中將模板參數作為返回值才考慮用, 平時也不必這樣定義函數.

參考 :

<< Effective Modern C++ >>

auto, auto&, const auto&以及其它形式的auto變種在for-range loop的選擇


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM