Shadi Hani乍一看, 這似乎是一個答案簡單的簡單問題:寫一個調用 Operator= 的構造函數不就行了:CFoo::CFoo(const CFoo& obj)
{
*this = obj;
}
或者,寫一個公用的拷貝方法,拷貝構造函數和 Operator= 都調用這個方法也行。就像這樣:
CFoo::CFoo(const CFoo& obj)
{
CopyObj(obj);
}
CFoo& CFoo::Operator=(const CFoo& rhs)
{
CopyObj(rhs);
return *this;
}
對於大多數類來說,這是行得通的,但還有些 特殊情況需要考慮。如果你的類包含有數據成員是另一個類的實例會怎樣呢?為了弄清楚這個問題,我寫了一個測試程序如 Figure 1 所示。 它有一個主類 CMainClass,它包含另一個類 CMember 的實例。兩個類都有拷貝構造函數和賦值操作,用 CMainClass 的拷貝構造函數調用 Operator=,如下面的代碼段所示。代碼中使用 printf 語句是為了顯示何時調用了哪個方法。為了運行構造函數,cctest 程序首先用缺省構 造函數創建 CMainClass 實例,然後用拷貝構造函數創建另一個實例:
CMainClass obj1;
CMainClass obj2 (obj1);
如果你編譯並運行 cctest,當構造 obj2 時,你會看到下面的 printf 信息:
CMember: default ctor
CMainClass: copy-ctor
CMainClass: Operator=
CMember: Operator=
成員對象 m_obj 被初始化了兩次!第一次是缺 省構造,第二次是賦值時再次被初始化。嘿,這是怎麼回事?
在 C++ 中,賦值和拷貝是不同的,因為拷貝構造函數是對未初始化的內 存進行初始化操作,而賦值是對現有的已經初始化的對象進行操作。如果你的類包含其它的類實例作為數據成員,那麼拷貝構造在調用 Operator=之前必須首先構造這些數據成員。其結果是致使這些成員就像 cctest 那樣被初始化兩次,明白了嗎?當你用賦值操作而不是初始化 例程進行成員初始化時,缺省構造函數也會發生同樣的事情。例如:
CFoo::CFoo()
{
m_obj = DEFAULT;
}
與下面代碼相對:
CFoo::CFoo() : m_obj(DEFAULT)
{
}
使用賦值方式,m_obj 被初始化兩 次,而用初始化例程語法,m_obj 只被初始化一次。所以,要如何避免拷貝構造期間額外的初始化呢?當它與你的代碼重用初衷相抵觸時, 最 好的解決俄u方法就是分開實現拷貝構造和賦值操作,即便它們做同樣的事情。從拷貝構造中調用 Operator= 肯定能行得通,但不是最有效率 的實現。我對初始化的建議是:
CFoo::CFoo(const CFoo& rhs) : m_obj(rhs.m_obj) {}
現在,主拷貝構造用初 始化例程調用成員對象的拷貝構造,並且 m_obj 只被其拷貝構造初始化一次。通常情況下,拷貝構造應該調用其成員的拷貝構造。賦值也是如 此。並且,它也同樣適用於基類:派生類的拷貝構造和賦值操作應該調用對應的基類方法。當然,有時因為一些具體情況,可能你的做法會有 所不同——這裡我所描述的是通用規則,只有在你遇到強制性原因時才會破壞這個規則。如果你要在基本對象被初始化之後完成一 些公共任務,可以將它們放到一個公共的初始化方法中,並在構造函數和 Operator= 中調用。