在C++中,函數返回整數或指針是通過eax寄存器進行傳遞的,理解起來比較簡單。
但是返回對象或結構體一直是令人感到困惑的問題。今天我整理了一下,將整個返回過程寫下來,以作備用。
還是先通過一個例子來理解這個問題:
首先,定義一個類Vector:
class Vector
{
public:
int x,y;
};
然后定義函數add()對Vector對象進行操作:
Vector add(Vector& a, Vector & b)
{
Vector v;
v.x = a.x + b.x;
v.y = a.y + b.y;
return v;
}
現在的問題是:
如果調用如下語句:
Vector a, b;
Vector c = add(a, b);
請問從a, b傳入函數開始,一共創建了多少個對象?
在通常情況下我們會做出如下分析:
1. 在add()函數中創建對象v。
2. 函數返回,創建一個臨時變量__temp0,并將v的值拷貝到__temp0中。
3. 最后創建對象c,通過操作符=,將__temp0中的對象拷貝到c中。
但其實,我們會在后面看到,整個過程就只創建了1個對象:c。
為了更清晰的分析整個調用過程,我們為Vector加上默認構造函數和拷貝構造函數,并增加一個靜態變量count用于統計構造函數調用次數:
class Vector
{
public:
static int count;
static void init()
{
count = 0;
}
int x,y;
Vector()
{
x = 0;
y = 0;
// For analysis.
count ++;
printf("Default Constructor was called.[0x%08x]\n", this);
}
Vector(const Vector & ref)
{
x = ref.x;
y = ref.y;
// For analysis.
count ++;
printf("Copy Constructor was called.[copy from 0x%08x to 0x%08x].\n", &ref, this);
}
};
int Vector::count = 0;
然后在main()函數中寫上調用代碼:
Vector a, b;
Vector::init();
printf("\n-- Test add() --\n");
Vector c = add(a, b);
printf("---- Constructors were called %d times. ----\n\n\n", Vector::count);
使用cl編譯。
(注:Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86, Microsoft (R) Incremental Linker Version 10.00.40219.01)
完成后,運行程序,得到如下結果:
-- Test add() --
Default Constructor was called.[0x0012fef8]
Copy Constructor was called.[copy from 0x0012fef8 to 0x0012ff60].
---- Constructors were called 2 times. ----
由此可知,在沒有優化的情況下,整個調用過程共創建了兩個對象:
即:c和__temp0.
整個調用過程偽代碼如下:
首先add()函數被編譯器看做:
void add(Vector& __result, Vector& a, Vector & b)
{
__result.x = a.x + b.x;
__result.y = a.y + b.y;
return;
}
而調用代碼同時被修改為:
Vector a, b;
Vector::init();
printf("\n-- Test add() --\n");
Vector __temp0; // 構造函數.
add(__temp0, a, b);
Vector c(__temp0); // 拷貝構造函數.
printf("---- Constructors were called %d times. ----\n\n\n", Vector::count);
現在就可以理解輸出結果了吧。
這里要強調一點,看到”=”并不等于調用了Operator=()的代碼,以下三種情況其實是等效的,都只調用了拷貝構造函數:
Vector b(a);
Vector b = a;
Vector b = Vector(a);
最精彩的部分在于,如果你用
cl /Ox
編譯代碼,使優化達到最大,再次運行,得到如下結果:
-- Test add() --
Default Constructor was called.[0x0012ff74]
---- Constructors were called 1 times. ----
這次,只調用了默認構造函數。這樣的修改被稱作Named Return Value(NRV) Optimization。
什么是NRV優化呢,顧名思義,就是保存返回值的變量不再使用沒名沒姓的__temp0這樣的東西了,而是直接把c作為返回變量,因此應該將NRV翻譯為“有名字的返回變量”吧,侯捷翻譯的《深入探索C++對象模型》居然把它稱為“具名數值”,真是不知所云。
言歸正傳,NVR優化的偽代碼如下:
Vector c;
add(c, a, b);
NVR優化的最大好處就是不會再去調用那次多余拷貝構造函數了(把__temp0拷貝到c),因此《深入探索C++對象模型》67頁最下面才會說第一版沒有拷貝構造函數,所以不能進行優化。其實是指優化的意義不大,或者說沒有什么可優化的。

但是這樣帶來的壞處是,如果你在拷貝構造函數里面放上與拷貝無關的代碼,比如我放入的printf和count++,那么這些東西就不會被調用了,產生優化前后代碼不一致問題。所以大家要在此注意一下。
http://www.linuxso.com/linuxbiancheng/5726.html