c語言中以下聲明一個結構體ios
typedef struct point3d{ float x; float y; float z;}Point3d;
struct point3d 轉化爲class Point3d以後c++
class Point3d { public: Point3d(float x = 0.0f, float y = 0.0f; float z = 0.0f) :_x(x),_y(y),_z(z){} private: float _x,_y,_y; }
封裝帶來的佈局成本增長了多少?實際是沒有增長佈局成本的。3個數據成員直接在class object內,member function在classs聲明卻不出如今class object中,所謂佈局的成本主要由virtual引發的。api
virtual function 機制用以支持運行時綁定(運行時多態)安全
virtual base class 機制支持屢次出如今集成體系中的base class有一個單一的被共享的實例。ide
nostatic data members 被配置在class object以內,static data member存放在class object以外.函數
static 和nostatic function memners放在class object以外佈局
virtual function的處理步驟:優化
聲明一個class Point而後查看其對象模型this
class Point { public: Point(float x); virtual ~Point(); float x() const; static int PointCount(); protected: virtual ostream& print(ostream& os) const; float _x; static int _point_count; }
c++支持單一繼承和多重繼承.base class subobject的data members直接被放置在derived class object,也就是說子類對象中包含基類子對象.基類成員的改變都會致使繼承類從新編譯.對於虛基類則是擴展子類本身的vittual table維護virtual base class的位置。編碼
class istream : virtual public ios{...}; class ostream : virtual public ios{...}; class iostream : public istream, public ostream{...};
在虛擬繼承的狀況下base class 無論在繼承鏈中被派生多少次,永遠只有一個實例存在即一個subobject.iostream之中只有virtual ios base class的一個實例.
函數返回基本是數據類型或者指針類型是經過eax寄存器進行傳遞的,返回對象對象則會進行命名返回值優化.之外部引用傳參的形式去掉函數內部的局部對象構造。
X foo(){ X xx X* px = new X(); xx.foo(); //func是一個虛函數 px->foo() delete px; rerurn xx }
如上函數有可能內部轉化爲以下代碼:
void foo(X &result){ _result.X::X(); px = _new(sizeof(X)); if(px != 0){ px->X::X(); } func(&_result);//這裏涉及到成員函數的語義 (*px->vtbl[2])(px) //使用virtual機制擴展px->func() if(px != 0) { (*px->[1])(px); //擴展delete px _delete(p) } return; }
若是類class A含有一個以上的類成員對象,編譯器會擴張構造函數,在構造函數中安插代碼,以成員類的聲明順序調用每一個成員類的默認構造函數,這些代碼被安插在用戶代碼以前.
有四種狀況會形成編譯器爲未聲明構造函數的類合成一個默認的構造函數,接着調用member object或者base class的默認構造函數,完成虛函數和虛基類機制。
類中沒有任何member或者base class object帶有拷貝構造函數,也沒有任何的虛函數和虛基類,默認狀況下 對象的初始化會展現按位拷貝,這樣效率很高且安全.
當對一個object作顯示初始化或者object被當作參數交給函數時以及函數返回一個object時(傳參、返回值、初始化)構造函數會被調用。
copy 構造函數不展示按位逐次拷貝的時候有編譯器產生出來,有四種狀況不展示:
一、2中編譯器講member或者bass class的拷貝構造哈數的調用安插到合成的拷貝構造函數中;3,4是爲了對vptr從新初始化.
class Shape{ public: Shape(){ memset(this, 0, sizeof(Shape);)} virtual ~Shape(); }
編譯器擴充構造函數的內容以下:
//擴充後的構造函數 Shape::Shape(){ //vptr在用戶代碼以前被設定 __vptr__Shape = __vtbl__Shape; //memset 會使vptr清0 memset(this, 0, sizeof(Shape)); }
編譯器會操做初始化列表,以成員的聲明順序子構造函數內部在用戶代碼以前安插初始化代碼.
當類含有一下四種狀況的時候會須要使用成員初始化列表:
class X{};
一個空類它隱藏1byte的大小,他是被編譯器安插進去的一個char,這使得這一class的兩個object在內存中配置有獨一無二的地址.
非靜態的數據成員直接存放在每個類對象中,對於繼承而來的費靜態成員也是如此。靜態數據成員則放在程序的全局數據段,且只存在一份數據實例.
對成員函數的分析,會在整個class聲明完成以後纔會出現.
在同一個訪問段中member的排列要符合較晚出現的成員在對象中有較高的地址,多個訪問段中的數據成員是自由排列的.
單一繼承下無佈局狀況下class和struct的佈局是同樣的.
Point3d中含有基類的子對象Point2d subobject,子類數據成員放置在基類子對象以後。
類體系以下
class Point2d { public: virtual ~Point2d(){}; protected: float _x,_y; }; class Point3d : public Point2d { public: //... protected: float _z; }; class Vertex { public: virtual ~Vertex(){}; protected: Vertex *next; } class Vertex3d: public Point3d, public Vertex { public: //... protected: float mumble; }
要存取第二個基類中的數據成員,將會是怎樣的狀況須要付出額外的成本嗎?不 ,成員的位置在編譯期就時就固定了,所以存取數據成員知識一個簡單的offset操做,就像單一繼承同樣簡單--不論是經由一個指針或者引用或者是一個對象來存取.
對於虛擬繼承主要的問題是如何存取class的共享部分,虛擬繼承使用兩種策略來實現:指針策略和offset策略.
爲了指出共享類對象每一個子類對象安插一些指針,每一個指針指向虛基類。
進一步的優化策略的實現:每個class object若是有一個或者多個virtual base classes,就會由編譯器安插一個指針指向virtual base class table.真正的虛基類指針放在虛基類表中.
在虛函數表中放置虛基類的offset.
基類的指針或者引用尋址出一個子類對象,虛函數分配表格索引,vptr指向virtual table, virtual table中存放虛函數指針.
inline是一個請求,編譯器解說就必須認爲它用一個表達式合理的將這個函數擴展開來,擴展期間使用實參代替形參,局部變量在封裝的區域內名字惟一.
float Point3d::getX()const{...} extern getX_Point3dFv(const Point3d* this) obj.getX() 等價於 getX_Point3dFv(&obj) ptr->getX() 等價於 getX_Point3dFv(ptr)
順序: 先父類後成員最後本身的調用方式.
vptr的初始化在全部base 類構造以後,初始化列表以前(程序代碼)
按照上面相反的順序調用
先本身析構而後類成員對象析構而後重置vptr而後基類析構而後虛基類析構
拷貝構造函數和拷貝複製運算符