拷貝構造
書曰,拷貝構造在下面三種情況下發生:
1.用一個對象初始化另一個對象
class A{};A a;A b(a);A b = a;
2.以對象對函數做值傳參
3.函數返回對象
臨時對象
為什麼研究臨時對象?
主要是為了提高程式的效能以及效率,因為臨時對象的構造與析構對系統開銷也是不小的,所以我們應該去瞭解它們,知道它們如何造成,從而儘可能去避免它們。
建立一個沒有命名的非堆對象會產生臨時對象。這種未命名的對象通常在三種條件下產生:為了使函數成功調用而進行隱式類型轉換時候、傳遞函數參數和函數返回對象時候。
拷貝構造的一些細節:
1.對於一個類X,如果一個建構函式的第一個參數是下列之一,且沒有其他參數或其他參數都有預設值,那麼這個函數是拷貝建構函式
a> X&
b> const X&
c> volatile X&
d> const volatile X&
2.類中可以存在超過一個拷貝建構函式
但是,如果一個類中只存在一個參數為X&的拷貝構造,那麼就不能用const X或volatile X的對象實行拷貝構造初始化
3.如果一個類中沒有定義拷貝建構函式,那麼編譯器會自動產生一個預設拷貝建構函式,參數可能是X::X(const X&)或X:X(X&),由編譯器根據上下文選擇哪一個
預設拷貝建構函式的行為:
跟建構函式一樣,執行先父類後子類的構造
對類中每個資料成員執行成員拷貝(memberwise copy):
a>如果資料成員是某個類的執行個體,那麼調用此類的拷貝建構函式
b>如果資料成員是一個數組,對數組的每一個執行按位拷貝(bitwise copy)
c>如果資料成員是一個數量(應該是指單一資料型別吧),如int、double,那麼調用系統內建的賦值運算子對其賦值
PS: 其實編譯器是否真的產生預設拷貝建構函式的代碼取決於拷貝構造是否具有必要性(nontrivial),並非所有類都會合成預設拷貝構造的代碼。當該類具備位拷貝屬性(bitwise copy semantics)時合成的拷貝構造是trivial的,此時合成拷貝構造代碼是沒必要的,編譯器會選擇bitwise copy,而不是memberwise copy:
a>類內含一個成員變數是類執行個體,而後者有設計者定義的拷貝構造或編譯器為其合成了拷貝構造(nontrivial)
b>類繼承自一個有拷貝構造的父類,後者的拷貝構造是設計者定義或編譯器為其合成的(nontrivial)
c>類聲明有virtual函數
d>類的父類串鏈中有virtual基類
4.拷貝建構函式不能由成員函數模板產生:
成員函數模板不改變語言的規則,而語言的規則說,如果程式需要一個拷貝建構函式而你沒有聲明它,那麼編譯器會為你自動產生一個。所以成員函數模板並不會阻止編譯器產生拷貝建構函式,賦值運算子多載也遵循同樣的規則。
下面貼一個比較費解的程式:
#include <stdio.h>#include <iostream>#include <string.h>using namespace std;class Internet{public: Internet() { printf("entered Internet::Internet(). addr(this) = 0x%lx.\n",(long)this); }; Internet(const char *name) { printf("entered Internet::Internet(const char*). addr(this) = 0x%lx.\n", (long)this); strcpy(this->name,name); } Internet(const Internet& temp) { printf("entered Internet::Internet(const Internet& temp). addr(this) = 0x%lx. addr(temp) = 0x%lx.\n", (long)this, (long)&temp); memset(this->name, 0, 20); } Internet& operator =(const Internet &temp) { cout << "entered Internet& operator =(const Internet& temp)" << endl; } ~Internet() { printf("entered Internet::~Internet(). addr(this) = 0x%lx.\n", (long)this); } char name[20];};Internet tp(){ Internet b("xiarendeniao"); cout << "b construct completed" << endl; return b;}int main(int argc, char** argv){ Internet a(tp()); cout << "a construct completed" << endl; cout << a.name << endl; //注意,拷貝構造把name清空了,這裡還是會有"xiarendeniao"輸出,說明實質沒有進入拷貝建構函式 cout << endl << "new test" << endl; Internet x("xiarendeniao"); Internet y(x); cout << y.name << endl; //注意,這裡輸出為空白就表示確實進入了拷貝構造 return 0;}
結果:
xudongsong@HP:~/c++_study$ g++ -o copy_constructor copy_constructor.cpp ; ./copy_constructor entered Internet::Internet(const char*). addr(this) = 0x7fff0b307830.b construct completeda construct completedxiarendeniaonew testentered Internet::Internet(const char*). addr(this) = 0x7fff0b307850.entered Internet::Internet(const Internet& temp). addr(this) = 0x7fff0b307870. addr(temp) = 0x7fff0b307850.entered Internet::~Internet(). addr(this) = 0x7fff0b307870.entered Internet::~Internet(). addr(this) = 0x7fff0b307850.entered Internet::~Internet(). addr(this) = 0x7fff0b307830.
當把上述程式的拷貝建構函式參數中的const去掉,編譯報錯,提示需要一個const Internet&為參數的拷貝構造,而只有一個Internet&為參數的拷貝構造可用
加上const之後,程式輸出結果卻表明對a的構造過程又沒有調用拷貝建構函式
我個人理解如下:編譯時間編譯器做拷貝構造的函數檢查,tp()需要返回一個const臨時變數用於對a作拷貝構造,所以要求有const Internet&為參的拷貝建構函式
編譯後期通過最佳化又把臨時變數和拷貝構造的過程都簡化掉了
那麼,問題出現了,如果我就是需要在Internet(const Internet& temp)這個拷貝構造裡面對對象的資料做一些操作比如深拷貝呢?豈不是無法實現?期待近期能遇到高人為我講解一下
其實,我本人很少讓拷貝構造發生,也儘力迴避這種隱晦的東西。對象執行個體都是一個個獨立的個體,拿一個個體初始化另一個個體很奇怪嘛(當然不排除這種需求的存在)。
虛擬繼承
當多重繼承時,多個父類是從同一個類繼承下來的,編譯會報錯,因為父類的可能有從同一個類繼承來的方法,編譯器不知道具體調用的是哪一個
這時需要用虛擬繼承來解決,虛擬繼承保證從你虛擬繼承的父類那裡只得到一份資料
http://wenku.baidu.com/view/7fc1d5ec856a561252d36fee.html
這個寫很好啊,從文法、語義、模型、效能、引用多方面進行了比較深的分析,http://www.cppblog.com/chemz/archive/2007/06/12/26135.html
下面是代碼驗證:
#include <iostream>using namespace std;class Vehicle //交通工具{public: Vehicle(int weight = 0) { this->weight = weight; } void SetWeight(int weight) { cout << "Vehicle::SetWeight()設定重量" << endl; this->weight = weight; } //virtual void ShowMe() = 0;protected: int weight;};class Car : public Vehicle //小汽車//class Car : public virtual Vehicle //小汽車{public: Car(int weight = 0, int aird = 0) : Vehicle(weight), aird(aird) {}; //void ShowMe() //{ // cout << "我是小汽車" << endl; //}protected: int aird;};class Boat : public Vehicle //輪船//class Boat : public virtual Vehicle //輪船{public: Boat(int weight = 0, float tonnage = 0) : Vehicle(weight), tonnage(tonnage) {}; //void ShowMe() //{ // cout << "我是輪船" << endl; //}protected: float tonnage;};class Amphibiancar : public Car, public Boat //水陸兩用汽車{public: Amphibiancar(int weight = 0, int aird = 0, float tonnage = 0) : Car(weight,aird), Boat(weight,tonnage) {}; //void ShowMe() //{ // cout << "我是水陸兩用汽車" << endl; //}};int main(int argc, char** argv){ Amphibiancar a(10,10,34.9f); a.SetWeight(3); //a.ShowMe(); //Vehicle* v = new Amphibiancar(45,45,34.8); //v->ShowMe();}
結果:
xudongsong@HP:~/c++_study$ g++ -o virtual_inherit virtual_inherit.cpp virtual_inherit.cpp: 在函數‘int main(int, char**)’中:virtual_inherit.cpp:70:4: 錯誤: 對成員‘SetWeight’的請求有歧義virtual_inherit.cpp:12:7: 錯誤: 備選為: void Vehicle::SetWeight(int)virtual_inherit.cpp:12:7: 錯誤: void Vehicle::SetWeight(int)
重載、覆蓋、隱藏 參考《高品質C++編程》
http://man.chinaunix.net/develop/c&c++/c/c.htm#_Toc520634042
重載
(1)相同的範圍(在同一個類中);
(2)函數名字相同;
(3)參數不同;
(4)virtual關鍵字可有可無。
覆蓋---指衍生類別函數覆蓋基類函數,特徵是:
(1)不同的範圍(分別位於衍生類別與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual關鍵字。
隱藏---衍生類別的函數屏蔽了與其同名的基類函數
(1)如果衍生類別的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
(2)如果衍生類別的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。
說白了,分別用父類指標bPtr和子類指標dPtr指向一個子類對象:
一般來講,bPtr調用的函數都是父類的函數、dPtr調用的函數都是子類函數;
如果bPtr想調用子類函數(多態、覆蓋)需要父子函數同名參數也相同、父類函數還得有virtual關鍵字;
子類的函數把父類同名不同參數的所有函數隱藏掉(dPtr不能調用從父類繼承來的與自身函數同名不同參數的所有函數),子類的函數把父類同名同參數但是沒有virtual關鍵字的函數也隱藏掉(對於有virtual的就是覆蓋和多態的概念了)。