B b;(1)
A a=b;(2)
A a=B();(3)
A a(b);(4)
如果(2)能執(zhí)行成功的話,那么需要滿足什么?(2)到底與(4)有什么區(qū)別?(4)能執(zhí)行成功的條件與(2)有什么區(qū)別?
回答完上述問題, 我順便還能回答一個(gè)問題: A(A& a)與A(const A& a)有什么區(qū)別?
下面就來回答這個(gè)問題:
首先來分析(2)是如何執(zhí)行的,分為以下步驟:
1. b能轉(zhuǎn)化為一個(gè)A的臨時(shí)對(duì)象;(轉(zhuǎn)化的方法可以是B自身包含一個(gè)到A的強(qiáng)制類型轉(zhuǎn)換操作符或者A有一個(gè)未指定explicit的參數(shù)為B或者B&或者const B&的構(gòu)造函數(shù)
2. 調(diào)用A的拷貝構(gòu)造函數(shù), 而且必須是A(const A& a), 如果沒有const是不行的, 至于原因我一會(huì)兒再說
再說說(3)執(zhí)行的步驟與條件:
1. B的這個(gè)臨時(shí)對(duì)象能轉(zhuǎn)化為一個(gè)A的臨時(shí)對(duì)象; (轉(zhuǎn)化的方法可以是B自身包含一個(gè)到A的強(qiáng)制類型轉(zhuǎn)換操作符或者A有一個(gè)未指定explicit的參數(shù)必須為B或者const B&的構(gòu)造函數(shù)
2. 調(diào)用A的拷貝構(gòu)造函數(shù), 而且必須是A(const A& a), 如果沒有const是不行的, 至于原因我一會(huì)兒再說
最后說說(4)執(zhí)行的條件:
A有參數(shù)為B或者B&或者const B&的構(gòu)造函數(shù);
或者是: B自身支持強(qiáng)制類型轉(zhuǎn)化為某種類型, 該類型能恰好為A的某一個(gè)構(gòu)造函數(shù)的參數(shù).
按照我上面的分析, 對(duì)于(2)和(3)如果B自身不提供轉(zhuǎn)化機(jī)制的話, 那么豈不是要調(diào)用A的構(gòu)造函數(shù)兩次了, 其實(shí)最后只調(diào)用一次就可以了, 也就是步驟2里調(diào)用A的拷貝構(gòu)造函數(shù)不會(huì)被調(diào)用, 但這只是編譯器作了優(yōu)化, 省掉了一次, 并不代表只執(zhí)行步驟1, 不信的話你把A拷貝構(gòu)造函數(shù)里的const去掉試試看, 要是能編譯過去才怪.
那說到根兒上, 加不加const修飾到底有啥區(qū)別呢?
其實(shí)這才是關(guān)鍵所在, 理解了這個(gè)也就知道本質(zhì)原因了, 下面我就來說說這個(gè)const:
首先要讓大家知道A(const A&)和A(A&)是兩個(gè)函數(shù)吧?也就是構(gòu)造函數(shù)的重載, 對(duì)于重載的函數(shù)參數(shù)類型或個(gè)數(shù)至少得有點(diǎn)不同, 這里我告訴你參數(shù)類型加不加const是完全不同的兩種參數(shù)類型.
知道了上面的概念, 再引用一段話:(出自http://www.xmsc.com.cn/InfoView/Article_122425.html)
說說左值和右值以及臨時(shí)對(duì)象的問題。也許你會(huì)說左值應(yīng)該就是能夠改變的變量,右值當(dāng)然就是不能改變的變量嘍!對(duì)嗎?對(duì)了一點(diǎn)點(diǎn),實(shí)際上左值是能夠被引用的變量,說的通俗點(diǎn)就是有名字的變量,你一定想到了些什么,對(duì)了,臨時(shí)變量就沒有名字,即使有你也不會(huì)知道,因?yàn)樗皇怯赡銊?chuàng)建的,編譯器會(huì)在內(nèi)部辨別它,但你并不知道,因此臨時(shí)變量不是左值,而是右值。你也許還會(huì)問,那const變量是不是左值呢?根據(jù)定義,它有名字,當(dāng)然就是左值了。因此左值并非一定“ 可被修改”。但是左值和右值與參數(shù)有什么關(guān)系嗎?我要告訴你的是:有,而且相當(dāng)密切,因?yàn)闃?biāo)準(zhǔn)c++規(guī)定:若傳遞給類型為引用的形參的實(shí)參是右值的話必須保證形參為const引用。
其實(shí)上面這段話最關(guān)鍵的地方在于指出了左值能被引用右值不能被引用, 臨時(shí)變量以及沒有顯示名字的變量都是右值, 因此在(2)或(3)中生成的A的臨時(shí)變量必定是右值, 而A的構(gòu)造函數(shù)必須為引用類型, 因此按照"若傳遞給類型為引用的形參的實(shí)參是右值的話必須保證形參為const引用"這個(gè)斷言, A的拷貝構(gòu)造函數(shù)里包含const也就是必須的了.
所以大家要消除一個(gè)固有的概念: 拷貝構(gòu)造函數(shù)就得A(const A&)這么寫, 完全錯(cuò)誤, 其實(shí)A(A&)有些情況下是必須有的, 沒有都不行, 比如auto_ptr, 因?yàn)樗谡{(diào)用拷貝構(gòu)造函數(shù)時(shí), 需要把自己當(dāng)前維護(hù)的那根指針銷毀掉(即設(shè)為NULL)同時(shí)把這根指針交給另一個(gè)auto_ptr保管.
下面的代碼是我自己實(shí)驗(yàn)的一個(gè)小例子:
using namespace std;
template <typename T>
class A
{
public:
A(){}
A(A& a)
{
cout<<"A(A& a)"<<endl;
}
template <typename T2>
A(const A<T2>& a)
{
cout<<typeid(T).name()<<endl;
cout<<typeid(T2).name()<<endl;
cout<<"A(const A<T2>&a)"<<endl;
}
};
int main(int argc, char* argv[])
{
A<int> a_int;
A<string> a=(a_int);
return 0;
}
輸出結(jié)果是:
Ss
i
A(A<T2>&a)
Ss
Ss
A(A<T2>&a)
如果把A的兩個(gè)拷貝構(gòu)造函數(shù)都加上const,或者把模板拷貝構(gòu)造函數(shù)的那個(gè)const移到非模板拷貝構(gòu)造函數(shù)上, 結(jié)果都會(huì)為:
Ss
i
A(A<T2>&a)
上述代碼是在linux的gcc3.4下通過的, 我相信大家應(yīng)該可以分析出原因來, 不過對(duì)于模板類我還是得羅唆幾句, 首先大家應(yīng)該清楚A<int>和A<string>是完全不同的兩個(gè)類型, 就像int和string一樣風(fēng)馬牛不相及, 只不過它們公用了一些模板代碼而已, 因此你把A<int>和A<string>看作兩個(gè)不同的類型來分析就應(yīng)該能分析透徹了.
在http://www.xmsc.com.cn/InfoView/Article_122425.html一文中還講解了auto_ptr中auto_ptr_ref的作用, 說到這里,讓我真的很恨auto_ptr源碼中的注釋啊,因?yàn)樗淖⑨層绣e(cuò)誤,一直誤導(dǎo)了我, 現(xiàn)在才體會(huì)到“錯(cuò)誤的注釋還不如沒有注釋”的真諦了。錯(cuò)誤出現(xiàn)在:
* auto_ptr<Derived> func_returning_auto_ptr(.....);
* ...
* auto_ptr<Base> ptr = func_returning_auto_ptr(.....);
其實(shí)應(yīng)該是:
* auto_ptr<Derived> func_returning_auto_ptr(.....);
* ...
* auto_ptr<Base> ptr(func_returning_auto_ptr(.....)); or auto_ptr<Derived> ptr = func_returning_auto_ptr ;
不要小看這點(diǎn)改動(dòng),上面的那種方案是編譯不過去的。因?yàn)樯厦娴姆桨咐镄枰D(zhuǎn)換兩次再調(diào)用一次拷貝構(gòu)造函數(shù),這違背了C++最多轉(zhuǎn)換一次的限制。
最后, 或許你在VC下實(shí)驗(yàn)的結(jié)果會(huì)和我說的不一樣, 這是因?yàn)閂C在臨時(shí)對(duì)象這一點(diǎn)上對(duì)標(biāo)準(zhǔn)C++的支持不夠好,用臨時(shí)對(duì)象作參數(shù)的時(shí)候不加const也可以編譯通過.