很久以前,就購買了《深度探索C++對象模型》這本書,剛入手時,翻看了很久,以爲受益不淺。最近發現,這本書中的知識,在腦海中,已經變得很不清晰了,本着好記性不如爛筆頭的傳統,打算用一段時間,把其中比較重要,尤爲是在實際工做中,真的會使用到的知識,作一番整理。今天開始第一篇,系統介紹下C++ 對象模型。ide
在簡單對象模型中,一個對象中,包含了一系列的slots。這些slot均爲一個指針,所指向的地址,對應的爲類中定義的對象或者方法。函數
好比定義一個類佈局
class foo { public: foo(); ~foo(); int number; int add(); };
那麼在定義一個對象obj
以後,obj
的結構以下:設計
| slot | meaning|
| --- | --- |
| 0 | pointer to foo() |
| 1 | pointer to ~foo() |
| 2 | pointer to number |
| 3 | pointer to add(int num) |指針
在這種模型下,可以避免由於成員類型的不一樣,而形成額外存儲空間的不一樣,也就是說,全部對象的大小,均爲 成員數量 x sizeof(pointer)
。code
注意,這種簡單的對象模型,這是一個設想,並無真的在C++語言中被採用。不過這種指向成員指針的思想卻在實際C++對象模型中得以繼承。
與簡單對象模型不一樣,表格驅動模型中,區分了成員變量,與成員函數。在一個對象中,會包含兩個指針,一個指針指向存儲了全部成員變量的表格,一個指針指向存儲了全部成員函數指針的表格。對象
好比定義一個類:繼承
class foo { public: foo(); ~foo(); int number; int count; int add(); int subtract(); int multiplied(); int divided(); };
那麼在定義個obj
以後,具體的存儲方式以下ip
pointer | table | table member |
---|---|---|
data pointer | data table | number |
count | ||
function pointer | function table | pointer to add |
pointer to multiplied | ||
pointer to divided |
注意,這種表格驅動對象模型也沒有被C++語言採用,不過
member function table
的觀念,被後續的virtual functions的實現所採納。
由Stroustrup最初設計的C++對象模型是從簡單模型演變而來的,其中:內存
非靜態成員變量(nonstatic data members)
被放置到對象中。靜態成員變量(static data members)
被放置在全部對象以外,單獨進行存儲。非virtual成員函數,包括靜態(static)與非靜態(nonstatic)
被放置在全部對象以外。vptr
的指針,該指針指向一個名爲virtual table
的表格,該表格存儲了類中定義的全部虛函數(virtual function)
好比定義一個類:
class foo { public: foo(); ~foo(); int number; static int count; int add(); static int subtract(); virtual int multiplied(); virtual int divided(); };
那麼定義一個obj
以後,具體的存儲方式以下:
location | member | table member |
---|---|---|
obj | number | --- |
vptr | pointer to multiplied() | |
pointer to diviede() | ||
out of obj | count | |
add() | ||
substract() |
在單一繼承的C++對象模型中,派生類對象中,會包含全部父類中的非靜態(nonstatic)成員變量
,可以訪問類中定義的全部靜態(static)變量
,靜態(static)與非靜態(nonstatic)的成員函數
,對於虛函數(virtual function)
,若是派生類中對虛函數作重寫(overwrite)
,則會覆蓋父類中虛表(virtual table)
中的函數指針。
class Point2D { public: int x; int y; static int count; int get_x(); int get_y(); virtual int foo(); static int get_count(); }; class Point3D_0 : public Point2D { public: int z; int get_z(); }; class Point3D_1 : public Point2D { public: int z; int get_z(); virtual int foo(); };
Point2D obj的內存佈局以下:
location | member | table member |
---|---|---|
obj | x | --- |
y | --- | |
vptr | pointer to Point2D::foo() | |
out of obj | Point2D::count | |
Point2D::get_x() | ||
Point2D::get_y() | ||
Point2D::get_count() |
Point3D_0 obj的內存佈局以下:
location | member | table member | table member |
---|---|---|---|
obj | Point2D | Point2D::vptr | pointer to Point2D::foo() |
Point2D::x | --- | ||
Point2D::y | --- | ||
z | --- | ||
out of obj | Point2D::count | ||
Point2D::get_x() | |||
Point2D::get_y() | |||
Point2D::get_count() |
Point3D_1 obj的內存佈局以下:
location | member | table member | table member |
---|---|---|---|
obj | Point2D | Point2D::vptr | pointer to Point3D::foo() |
Point2D::x | --- | ||
Point2D::y | --- | ||
z | --- | ||
out of obj | Point2D::count | ||
Point2D::get_x() | |||
Point2D::get_y() | |||
Point2D::get_count() |
在多繼承(非菱形繼承)中,派生類對象會包含全部基類中的非靜態(nonstatic)成員變量
,可以訪問類中定義的全部靜態(static)變量
,靜態(static)與非靜態(nonstatic)的成員函數
。可是這裏須要注意的是全部基類中的靜態(static)與非靜態(nonstatic)變量或者函數方法的名字,不可以出現衝突
,不然會形成派生類對象調用時的不明確,編譯器會直接報錯。
對於虛函數(virtual function)
,派生類若是沒有對父類中的虛函數(virtual function)
作重寫(overwrite),那麼全部父類中的虛函數不能衝突,不然因爲調用不明確出現編譯錯誤。若是派生類對父類中的虛函數(virtual function)
作了重寫(overwrite),那麼子類的虛函數被放在聲明的第一個基類的虛函數表中。
class base1 { private: int a1{ 1 }; public: virtual void print() { std::cout << "base 1" << std::endl; } int get_1() { return a1; }; }; class base2 { private: int a2{ 2 }; public: virtual void print() { std::cout << "base 2" << std::endl; } int get_2() { return a2; }; }; class child : public base1, public base2 { private: int c{ 0 }; public: virtual void print() { std::cout << "child" << std::endl; } };
child obj 內存佈局
location | member | member | member |
---|---|---|---|
obj | base1 | base1::vptr | pointer to chlid::print() |
base1::a1 | --- | ||
base2 | base1::vptr | pointer to base2::print() | |
base2::a2 | --- | ||
c | --- | --- | |
out of obj | base1::get_1() | --- | --- |
base2::get_2() | --- | --- |
菱形繼承指的是基類被某個派生類簡單重複繼承了屢次,從而會在派生類中出現多個基類實例。這種狀況下,派生類對象去調用基類中成員變量時,就會出現調用的不明確,從而致使程序的行爲不可測。爲了解決這種問題,C++對象模型中引入了虛繼承的概念。
在虛繼承中,派生類會生成一個隱藏的虛基類指針(vbptr)
用於存放最開始的父類。結構以下:
class base { public: int a{ 0 }; int b{ 1 }; }; class base1 : public virtual base { public: base1() { a = 1; } virtual void print() { std::cout << "base 1" << std::endl; } }; class base2 : public virtual base { public: base2() { a = 2; } virtual void print() { std::cout << "base 2" << std::endl; } }; class child : public base1, public base2 { private: int c { 10 }; public: virtual void print() { std::cout << "child: " << a << std::endl; } };
child obj內存佈局
location | member | member | member |
---|---|---|---|
obj | base1 | vbptr, point to child::base | --- |
base1::vptr | pointer to child::print() | ||
base2 | vbptr, point to child::base | --- | |
base2::vptr | pointer to base2::print() | ||
base | a | --- | |
b | --- | ||
c | --- | --- |