關於傳值與傳引用的討論


效率問題

對於用戶自定義的類型來說,傳引用一般要比傳值高效。傳引用不需要經過對象過程,在《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()時,其匯編代碼為:

可以看到,傳引用比傳值多了一次尋址操作,這是因為引用的實現往往基於指針,因此傳引用通常意味着真正傳遞的是指針。

總結:

  1. 對內置類型來說,通常傳值更高效。
  2. 對用於自定義類型來所,傳值要經歷構造與析構過程,一般比較耗時。

對象的切割問題

傳值有時會引起對象的切割問題。就上面所示的兩個類,當我們定義如下函數並進行傳值調用時:

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 和指針那樣。如上所示,傳值是可以比傳引用快的沒有,因為它少了一次解引用的操作。

(完)


免責聲明!

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



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