1. 程式人生 > 其它 >C++函式按引用傳值與值傳值分析

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.結論

函式傳參時儘量以引用傳值代替值傳值,以引用傳值比值傳值更加高效,並且可以避免切割問題。