[C++]C++ 對象模型

很久以前,就購買了《深度探索C++對象模型》這本書,剛入手時,翻看了很久,以爲受益不淺。最近發現,這本書中的知識,在腦海中,已經變得很不清晰了,本着好記性不如爛筆頭的傳統,打算用一段時間,把其中比較重要,尤爲是在實際工做中,真的會使用到的知識,作一番整理。今天開始第一篇,系統介紹下C++ 對象模型。ide

簡單對象模型(A Simple object model)

在簡單對象模型中,一個對象中,包含了一系列的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++對象模型中得以繼承。

表格驅動對象模型( A Table-driven Object Model)

與簡單對象模型不一樣,表格驅動模型中,區分了成員變量,與成員函數。在一個對象中,會包含兩個指針,一個指針指向存儲了全部成員變量的表格,一個指針指向存儲了全部成員函數指針的表格。對象

好比定義一個類:繼承

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的實現所採納。

C++對象模型

由Stroustrup最初設計的C++對象模型是從簡單模型演變而來的,其中:內存

  • 非靜態成員變量(nonstatic data members)被放置到對象中。
  • 靜態成員變量(static data members)被放置在全部對象以外,單獨進行存儲。
  • 非virtual成員函數,包括靜態(static)與非靜態(nonstatic)被放置在全部對象以外。
  • 每一個對象中都增長一個名爲vptr的指針,該指針指向一個名爲virtual table的表格,該表格存儲了類中定義的全部虛函數(virtual function)

非繼承下的C++對象模型

好比定義一個類:

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++對象模型

在單一繼承的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()

多繼承(非菱形繼承)下的C++對象模型

在多繼承(非菱形繼承)中,派生類對象會包含全部基類中的非靜態(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++對象模型

菱形繼承指的是基類被某個派生類簡單重複繼承了屢次,從而會在派生類中出現多個基類實例。這種狀況下,派生類對象去調用基類中成員變量時,就會出現調用的不明確,從而致使程序的行爲不可測。爲了解決這種問題,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 --- ---
相關文章
相關標籤/搜索