效率問題
對於用戶自定義的類型來說,傳引用一般要比傳值高效。傳引用不需要經過對象過程,在《Effective C++》中作者舉了個例子:
class Base
{
pubilc:
Base();
~Base();
private:
std::string b1;
std::string b2;
}
class Derive : public Base
{
public:
Derive();
~Derive();
private:
std :: string d1;
std ::string d2;
}
此刻我們擁有一個派生類對象derive 。對derive傳值的結果是共需要進行六次的構造函數的調用:對象本身的構造函數,對象內部數據成員string的構造,對象基類部分的構造函數,對象基類數據成員string的構造。同樣的,待對象生命結束后,還要經歷6次析構函數的調用。而傳引用則不用進過如此多的構造與析構,甚至一次都不用。
對內置類型來說,傳值的效率往往要高於傳引用。內置類型包括了int,float ,double,指針類型等等。看下面的程序:
void f(int i)
{
i = i + 1;
}
void g(int & i)
{
i = i + 1;
}
int main()
{
int i=10;
f(i);
g(i);
}
當通過傳值調用函數f()時,其匯編代碼為:
當通過傳引用調用函數g()時,其匯編代碼為:
可以看到,傳引用比傳值多了一次尋址操作,這是因為引用的實現往往基於指針,因此傳引用通常意味着真正傳遞的是指針。
總結:
- 對內置類型來說,通常傳值更高效。
- 對用於自定義類型來所,傳值要經歷構造與析構過程,一般比較耗時。
對象的切割問題
傳值有時會引起對象的切割問題。就上面所示的兩個類,當我們定義如下函數並進行傳值調用時:
void func(Base B)
{
...
}
int main()
{
Derive A;
func(A);
}
編譯器將調用Base類的復制構造函數來初始化B,初始化數據來源於A,但構造結果是個Base類對象:也就意味着A的特有部分的數據被切割掉了。在函數func中,B的行為是一個Base的行為而不是Derive的行為。
通過傳引用,能夠避免切割的問題:
void func(Base & B)
{
...
}
int main()
{
Derive A;
func(A);
}
此時在函數func內,對象B實際類型為Derive,通俗地將,B就是A。若希望在func中使用到實參的特性,傳引用能夠保證這一點。
STL中的使用情況
如果你打開STL中的源碼,你會發現容器的iterator都是通過傳值形式傳參。在《Effective C++》中指出,對於內置類型、STL 迭代器和 STL 仿函數,pass-by-value 也是可以的,一般沒有性能損失。在 x86-64 上,對於只有一個 指針成員且沒有自定義復制構造函數的類,傳值是可以通過寄存器進行的,就像傳遞普通 int 和指針那樣。如上所示,傳值是可以比傳引用快的沒有,因為它少了一次解引用的操作。
(完)