1. 程式人生 > >2.4 成員們的初始化佇列(Member Initialization List)

2.4 成員們的初始化佇列(Member Initialization List)

當你寫下一個constructor時,就有機會設定class members的初值。為了讓程式能夠被順利編譯,有4種情況你必須使用member initialization list:

  1. 當初始化一個reference member時;
  2. 當初始化一個const member時;
  3. 當呼叫一個base class的constructor,而它有用一組引數時;
  4. 當呼叫一個member class 的constructor,而它有用一組引數時。

當著4種環境,你使用constructor函式體內初始化,程式可以被正確編譯並執行,但是效率不高。例如:

class Word
{
	String _name;
	int _cnt;
public:
    //沒有錯誤,效率不佳
	Word()
	{
		_name = 0;
		_cnt = 0;
	}
};

在這裡,Word constructor會先產生一個臨時的String Object,然後將它初始化,之後以一個assignment運算子將臨時object指定給_name,隨後再摧毀臨時性的object。下面是constructor可能的內部擴張:

Word::Word()
{
	//呼叫String的default constructor
	_name.String::String();
	
	//產生臨時物件
	String temp = String(0);
	
	//“memberwise”地拷貝_name
	_name.String::operator=(temp);
	
	//摧毀臨時物件
	temp.String::~String();
	
	_cnt = 0;
}

如果使用member initialization list初始化,效率會明顯提高:

//較佳的效率
Word::Word():_name(0)
{
	_cnt = 0;
}

member initialization list中的專案順序是由class的members宣告順序決定的,不是由initialization list中的排列順序決定的。有些程式設計師堅持所有的member初始化操作必須在member initialization list中完成,甚至是內建型別的member。

初始化順序和initialization list中的專案排列順序之間的外觀錯亂,會導致下面意想不到的危險:

class X
{
	int i;
	int j;
public:
	X(int val):
		j(val),i(j)
	{}
	//...
};

上述程式碼看起像是把j設定為val,再把i的值設定為j。問題在於,宣告順序的緣故,initialization list中的i(j)比起j(val)執行更早。所以i(j)的值未定義的。

這個“臭蟲”的困難度在於不容易被觀察出來。要不就如下面的放在constructor中:

class X
{
	int i;
	int j;
public:
	X(int val):
		j(val)
	{
		i = j;
	}
	//...
};

當constructor擴張時候,因為initialization list的專案被編譯器安插到explicit user code之前,所以上述程式碼能正確賦值。

另一個常見問題是,是否你能夠像下面一樣,呼叫一個member function以設定一個member初值:

//X::xfoo()被呼叫,這樣好嗎?
X::X(int val)
	:i(xfoo(val)),j(val)
{}

其中xfoo()是X的一個member function。忠告是請使用“存在於constructor體內的一個member”,不要使用“存在於member initialization list中的member”,來為另一個member 設定初值。你並不知道xfoo()對X object 依賴度多高,如果你把xfoo()放在constructor裡面,那麼對於“到底是哪一個member 在xfoo()執行時被設定初值”這件事,可以確保不會發生模凌兩可的情況。

Member function的使用是合法的(當然我們必須不考慮它使用的的member function是否被初始化),這是因為和此object相關的this指標已經被建構妥當,而constructor大約被擴充為:

X::X(/*this pointer*,/int val)
{
	i = this->xfoo(val);
	j = val;
}

最後,如果一個derived class member function被呼叫,其返回值被當作base class constructor的一個引數,將會如何:

//呼叫FooBar::fval()可以嗎?
class FooBar: public X
{
	int _fval;
public:
	int fval(){return _fval;};
FooBar(int val)
	:_fval(val),
	X(fval())	//fval()作為base class constructor引數
};

它可能的擴張結果是:

FooBar::FooBar(/*this pointer goes here*/)
{
	X::X(this,this->fval());
	_fval = val;
}

它的確不是一個好主意。

簡答的講,編譯器會對initialization list 一一處理並可能重新排序,以反應出member 的宣告次序。它會安插一些程式碼到constructor體內,並置於任何explicit user code之前。