c++函數(shù)參數(shù)和返回值
c++一直以來是一個關(guān)注效率的代碼,這樣關(guān)于函數(shù)的參數(shù)傳遞和返回值的接收,是重中之重。下文提供了一些個人的見解。
函數(shù)存儲位置
函數(shù)參數(shù)在編譯期展開,目前各平臺的編譯期均有不同。
名稱 | 存儲位置 |
---|---|
函數(shù)名稱和邏輯 | 代碼段存儲 |
函數(shù)參數(shù)和返回值 | 棧中或者寄存器(64位會有6個寄存器使用) |
new malloc 的變量 | 堆 |
函數(shù)參數(shù)入棧順序
微軟有幾種編譯期屬性,用來定義函數(shù)參數(shù)的順序和堆棧。
關(guān)鍵字 | 堆棧清理 | 參數(shù)傳遞 |
---|---|---|
__cdecl | 調(diào)用方 | 在堆棧上按相反順序推送參數(shù)(從右到左) |
__clrcall | 不適用 | 按順序?qū)?shù)加載到 clr 表達式堆棧上(從左到右)。 |
__stdcall | 被調(diào)用方 | 在堆棧上按相反順序推送參數(shù)(從右到左) |
__fastcall | 被調(diào)用方 | 存儲在寄存器中,然后在堆棧上推送 |
__thiscall | 被調(diào)用方 | 在堆棧上推送;存儲在 ecx 中的 this 指針 |
__vectorcall | 被調(diào)用方 | 存儲在寄存器中,然后按相反順序在堆棧上推送(從右到左) |
所以直接在函數(shù)參數(shù)上,調(diào)用表達式和函數(shù)來回去值的話,非常危險
初始化列表
class init1 { public: void print() { std::cout << a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; } int c, a, b; };
a這個類,可以通過 a a{1,2,3}; 來初始化對象。
看著很美好,但是有幾個問題需要注意。
參數(shù)是的入棧順序是跟著類的屬性的順序一致, 當前是 c, a, b;
int i = 0; init1 a = {i++, i++, i++}; a.print();
當我如此調(diào)用的時候,得到的返回值是1 2 0i++的執(zhí)行順序是從左到右,跟函數(shù)調(diào)用順序無關(guān)。 另外不能有 構(gòu)造函數(shù)
class init1 { public: init1(int ia, int ib, int ic) { std::cout << "construct" << std::endl; a = ia; b = ib; c = ic; } init1(const init1& other) { std::cout << "copy " << std::endl; a = other.a; b = other.b; c = other.c; } void print() { std::cout << a << std::endl; std::cout << b << std::endl; std::cout << c << std::endl; } int c, a, b; };
當我添加了構(gòu)造函數(shù)的時候。 用下面代碼測試。會得到兩種結(jié)果
void test_initilizelist() { int i = 0; //init1 a = { i++, i++, i++ }; // 0 1 2 init1 a(i++, i++, i++); // 2 1 0 a.print(); }
函數(shù)的返回值
函數(shù)返回值的聲明周期在函數(shù)體內(nèi)。
用參數(shù)引用來返回
class result { public: int result; }; void getresult(result& result) ...
優(yōu)點:
- 效率最高,因為返回值的對象在函數(shù)體外構(gòu)造,可以一直套用, 可以一處構(gòu)造,一直使用。
- 安全,可以定義對象,并不用new或者malloc, 沒有野指針困擾。
缺點: - 代碼可讀性低,不夠優(yōu)美
- 無法返回nullptr. 一般在 result 中定義一個; 用來表示一個空對象。
- 容易賦值到一個臨時對象中,當調(diào)用getresult({1})會賦值到一個 臨時的 result 對象中,拿不到返回值。正常來說也不會這樣做。
返回一個參數(shù)指針
class result { public: int result; }; result* getresult() ...
優(yōu)點:
- 簡潔明了
- 參數(shù)傳遞快速
缺點: - 指針如果在 函數(shù)內(nèi) static 需要考慮多線程。 如果是 new 出來的,多次調(diào)用效率不高
- 指針無法重復(fù)使用,(可以用 std::share_ptr 增加對象池來解決問題。但會引入新的復(fù)雜度。)
- 需要考慮釋放的問題
返回一個對象
class result { public: int result; }; result getresult() ...
優(yōu)點:
- 沒有內(nèi)存泄露的風險
- 簡潔明了
缺點: - 但有個別編譯期優(yōu)化選項問題,會導(dǎo)致一次構(gòu)造兩次拷貝, 第一次是函數(shù)體內(nèi)對象向返回值拷貝,第二次是 返回值拷貝給外面接收參數(shù)的。
- 開啟編譯期優(yōu)化選項,并且是 在 return result 的時候構(gòu)造返回對象,才能優(yōu)化。
總結(jié)
一般如果是 簡單結(jié)構(gòu)體,用 返回一個臨時對象的方式解決。
如果使用 返回一個參數(shù)指針,一般改成返回一個id,用一個manager來管理內(nèi)存機制?;蛘?共享內(nèi)存,內(nèi)存池來解決內(nèi)存泄露后續(xù)的問題
用 參數(shù)引用來返回的話,一般會這么定義int getresult(result& result)函數(shù)返回值,用來返回狀態(tài)碼,真正的數(shù)據(jù),放到 result 中。
函數(shù)的幾種變體
inline 函數(shù)
- inline 函數(shù)是內(nèi)聯(lián)函數(shù),是編譯期優(yōu)化的一種手段,一般是直接展開到調(diào)用者代碼里,減少函數(shù)堆棧的開銷。
- inline 標識只是建議,并不是一定開啟內(nèi)聯(lián)。
- 函數(shù)比較復(fù)雜或者遞歸有可能編譯期不展開。
- dll 導(dǎo)出的時候,可以不用加導(dǎo)出標識,會直接導(dǎo)出到目標處。
- inline 在msvc的平臺,只要實現(xiàn)頭文件中,加不加內(nèi)聯(lián)是一樣的. (警告頂級調(diào)到最高/wall, 不加inline標識的函數(shù)會提示,未使用的內(nèi)聯(lián)函數(shù)將被刪除。)
- inline 函數(shù)比全局函數(shù)更快,但是全局函數(shù)無法定義在頭文件中(會報多重定義函數(shù)。)所以一般用class 包一層 static inline 函數(shù),用來寫工具類。
函數(shù)對象
class a { public : int value; int operator() (int val) { return value + val; } }
上述代碼是一個函數(shù)對象,重載operator()得到一個函數(shù)對象。
int a = a{10}(1)會返回11, 顯示構(gòu)造了一個a{value=10}的對象,然后調(diào)用重載函數(shù)operator(), 返回 10 + 1 = 11
上述代碼因為是在頭文件實現(xiàn)的,所以編譯期會自動把operator()函數(shù)當成inline函數(shù),執(zhí)行效率很高。
lambda 函數(shù)
lambda 其實就是一個函數(shù)對象,會在編譯期展開成一個函數(shù)對象體。