C++函式按引用傳值與值傳值分析
本文是學習《Effective C++》的筆記,記錄函式引用傳值與值傳值在效率上面的區別。
1.效率分析
考慮以下程式碼:
class Fruit
{
public:
virtual void ShowName()const;
private:
std::string strName;
};
class Apple:public Fruit
{
public:
virtual void ShowName() const;
private:
std::string strArea;
};
void PrintFruitName(Apple apple)
{
// do something
}
int main(int argc, char* argv[])
{
Apple apple;
PrintFruitName(apple);
return 0;
}
首先建立Apple物件,然後函式PrintFruitName(Apple apple)以值傳遞引數,將實參轉化為形參,會以apple為藍本呼叫Apple的拷貝建構函式,依次構造基類Fruit的std::string成員物件strName,基類Fruit物件,子類Apple的std::string成員物件strArea以及子類Apple自身。
函式PrintFruitName執行完成後,會按照構造順序的相反順序依次析構子類Apple,Apple成員是std::string物件,基類Fruit,Fruit的std::string物件,也就是執行此函式會造成四次構造與四次解構函式的呼叫。
當以引用傳值時:
void PrintFruitName(const Apple& apple)
{
// do something
}
沒有任何建構函式與解構函式呼叫,效率會高很多,這裡的const也是十分必要的,原先以值傳遞Apple物件,PrintFruitName函式也只能對其副本形參進行修改,不會影響傳入的引數。現在是const Apple& 引用傳參,則用const關鍵字明確告訴呼叫者函式不會改變傳入的Apple引數,並且提升效率,和按值傳遞達到一樣的效果。
2.物件切割問題
首先分析以下這段程式碼的輸出結果:
#include <iostream>
class Fruit
{
public:
virtual void ShowName()const;
private:
std::string strName;
};
void Fruit::ShowName()const
{
std::cout << "Fruit!!!" << std::endl;
}
class Apple:public Fruit
{
public:
virtual void ShowName() const;
};
void Apple::ShowName() const
{
std::cout << "Apple!!!" << std::endl;
}
void PrintFruitName(Fruit fruit)
{
fruit.ShowName();
}
int main(int argc, char* argv[])
{
Apple apple;
PrintFruitName(apple);
return 0;
}
基礎不紮實的我認為由於多型,子類物件轉化為基類物件,執行虛擬函式時會呼叫子類的函式實現,會輸出 “Apple!!!”
VS2019的輸出結果為:
What!!!怎麼和想得不一樣?
果然我的基礎不夠紮實,原來以值傳遞引數會產生物件的切割問題,當子類物件Apple,傳遞給PrintFruitName(Fruit fruit)時,要呼叫fruit的拷貝建構函式,即:Fruit(apple),以子類物件拷貝構造基類物件,造成子類Apple的特化性質被切割掉,僅僅留下基類Fruit物件。
這是由於函式形參真正是由Fruit的copy建構函式構造了它, 在呼叫opy建構函式Fruit(apple)時僅僅以apple的基類部分來構造Fruit。所以形參僅僅表現為基類的性質,不存在多型,所以輸出"Furit!!!"。
改成以引用傳值,則和預想一直,輸出"Apple !!!"
void PrintFruitName(const Fruit& fruit)
{
fruit.ShowName();
}
3.內建資料型別
而對於內建資料型別,迭代器,函式物件都習慣被設計為用值傳遞。
4.結論
函式傳參時儘量以引用傳值代替值傳值,以引用傳值比值傳值更加高效,並且可以避免切割問題。