C ++のひし形の継承オブジェクトメモリレイアウトの実用的な説明と分析

この記事では、主にC ++オブジェクトモデルでのダイヤモンド継承のオブジェクトモデルについて説明し、基本クラスのオブジェクト変数と関数の継承についてそれぞれ説明します。
ダイヤモンド継承とは:
ダイヤモンド継承とは、基本クラス(Base)が2つの派生クラス(Derived1、Derived2)を派生し、次にこれら2つの派生クラス(Derived1、Derived2)が1.1などの最終的な派生クラスを派生することを意味します。 。

1.ダイヤモンド継承の非仮想継承

1.1クラスBase、派生クラスDerived1、派生クラスDerived2、および最終派生クラスDDerivedのUML構造図

ここに画像の説明を挿入

1.2クラスBase、派生クラスDerived1、派生クラスDerived2、および最終派生クラスDDerivedのコード定義

NonVirtualDerivedDiamondClass.cpp

#include <iostream>

using namespace std;

class Base
{
    
    
public:
	Base(int x) : x(x) {
    
    }

protected:
	int x;

};

class Derived1 : public Base
{
    
    
public:
	Derived1(int y1) : Base(1), y1(y1) {
    
    }

protected:
	int y1;
};

class Derived2 : public Base
{
    
    
public:
	Derived2(int y2) : Base(1), y2(y2) {
    
    }

protected:
	int y2;
};

class DDerived : public Derived1, public Derived2
{
    
    
public:
	DDerived(int z) : Derived1(11), Derived2(22), z(z) {
    
    }
	void callX()
	{
    
    
		cout << this->x << endl;
	}

protected:
	int z;
};

1.3最終的な派生クラスDDerivedオブジェクトモデル

最終派生クラスには、基本クラスBase、派生クラスDerived1、およびDerived2のオブジェクトモデルが含まれているため、分析できるのは最終派生クラスDDerivedオブジェクトモデルのみです。
C ++オブジェクトモデルメソッドを表示するVS2017開発者モードは、このブログを参照できます:C ++単一継承クラスオブジェクトメモリレイアウトの実用的な説明と分析

  • メモリ内のDDerivedのレイアウト
    ここに画像の説明を挿入
    上の図からわかるように、ダイヤモンド継承派生クラスDerived1とDerived2のメモリは、それぞれ基本クラスBaseのメンバー変数intxを継承して保存します。最終的な派生クラスDDerivedでメンバー変数xを呼び出すと、あいまいさが生じます。DDerivedは、どのオブジェクトxが呼び出されているかを認識していません。このとき、コンパイルでエラーが報告され、次の図に示すように、メンバー変数xはあいまいと呼ばれます。
    ここに画像の説明を挿入
    最終的な派生クラスDerivedで継承されたxを呼び出す場合は、指定された呼び出しのスコープ( "::")修飾子を表示して、継承されたxが呼び出される基本クラスを示す必要があります。つまり、this-> Derived2: :x、次のコードに示すように:
class DDerived : public Derived1, public Derived2
{
    
    
public:
	DDerived(int z) : Derived1(11), Derived2(22), z(z) {
    
    }
	void callX()
	{
    
    
		cout << this->Derived2::x << endl; // 显示指定作用域Derived2::x,调用Derived2的成员变量x
	}

protected:
	int z;
};

D派生クラスのメモリレイアウトから、派生クラスDerived1とDerived2のクラスオブジェクトはそれぞれ、基本クラスBaseから継承されたメンバー変数int xを格納していることがわかります。これにより、最終派生クラスの変数xを取得する際にあいまいさが生じるだけではありません。クラスD派生、同時にそれはメモリの浪費を引き起こします。それで、これらの問題を解決する方法はありますか?答えはイエスです。それは仮想継承を使用することです。

2.ダイヤモンド継承の仮想継承

2.1クラスBase、派生クラスDerived1、派生クラスDerived2、および最終派生クラスDDerivedのUML構造図

ここに画像の説明を挿入
上の図からわかるように、派生クラスDerived1と派生クラスDerived2のみが、クラスBaseから継承するときに仮想継承を使用しますが、最終的な派生クラスDDerivedは、通常の継承を使用してDerived1とDerived2を継承します。これは

Derived1 : public virtual Base {
    
     ... };
Derived2 : public virtual Base {
    
     ... };
DDerived : public Derived1, public Derived2 {
    
     ... };

2.2クラスBase、派生クラスDerived1、派生クラスDerived2、および最終派生クラスDDerivedのコード定義

VirtualDerivedDiamondClass.cpp

#include <iostream>

using namespace std;

class Base
{
    
    
public:
	Base() = default;
	Base(int x) : x(x) {
    
    }

protected:
	int x;

};

class Derived1 : public virtual Base
{
    
    
public:
	Derived1(int y1) : Base(1), y1(y1) {
    
    }

protected:
	int y1;
};

class Derived2 : public virtual Base
{
    
    
public:
	Derived2(int y2) : Base(1), y2(y2) {
    
    }

protected:
	int y2;
};

class DDerived : public Derived1, public Derived2
{
    
    
public:
	DDerived(int z) : Derived1(11), Derived2(22), z(z) {
    
    }
	void callX()
	{
    
    
		cout << this->Derived2::x << endl;
	}

protected:
	int z;
};

2.3最終的な派生クラスDDerivedオブジェクトモデル

最終派生クラスには、基本クラスBase、派生クラスDerived1、およびDerived2のオブジェクトモデルが含まれているため、分析できるのは最終派生クラスDDerivedオブジェクトモデルのみです。
C ++オブジェクトモデルメソッドを表示するVS2017開発者モードは、このブログを参照できます:C ++単一継承クラスオブジェクトメモリレイアウトの実用的な説明と分析

  • メモリ内のDDerivedレイアウトここに画像の説明を挿入
    は図から明らかであり、派生クラスDDerived最終オブジェクトモデル、派生クラスおよび派生クラスDerived1 Derived2には、基本クラスBase int x;メモリのメンバー変数はありませんが、複数の仮想ポインタがあります。仮想ポインタはそれぞれ、それぞれの仮想関数テーブルを指します。変数xのオフセットアドレスは、仮想関数テーブルに格納されます。オフセットアドレスによって、派生クラスDerived1と派生クラスDerived2は変数xを取得できます。この時点で、次の図に示すように、派生クラスはthisポインターを直接使用して、あいまいさなしに変数xを呼び出すことができます。
    ここに画像の説明を挿入
    したがって、仮想継承は、主に基本クラスのメンバー変数のオフセットアドレスを継承することです。オフセットアドレスは、仮想ポインタが指す仮想関数テーブルに格納され、配置の順序は変数の宣言順序に従います。 。次の図に示すように、
    ここに画像の説明を挿入
    仮想継承クラスには仮想関数がないため、基本クラスに仮想関数がある場合、仮想継承後のダイヤモンド継承の最終派生クラスのクラスオブジェクトモデルは何ですか?次に、分析と議論を続けます。

第三に、基本クラスの仮想関数を使用したダイヤモンド継承の仮想継承

3.1クラスBase、派生クラスDerived1、派生クラスDerived2、および最終派生クラスDDerivedのUML構造図

ここに画像の説明を挿入
上の図からわかるように、基本クラスBaseと派生クラスDerived1およびDerived2には、仮想デストラクタと仮想関数vfun1();があり、これが継承の仮想関数を持つクラス、つまり、非PODタイプ、およびメモリオブジェクトmemcpy(...)をバイトごとにコピーすることはできません。

3.2クラスBase、派生クラスDerived1、派生クラスDerived2、および最終派生クラスDDerivedのコード定義

#include <iostream>

using namespace std;

class Base
{
    
    
public:
	Base() = default;
	virtual ~Base() {
    
    }
	
	Base(int x) : x(x) {
    
    }

protected:
	int x;

private:
	virtual void vfun1() = 0;
};

class Derived1 : public virtual Base
{
    
    
public:
	Derived1(int y1) : Base(1), y1(y1) {
    
    }
	virtual ~Derived1() {
    
    }
	virtual void vfun1() override
	{
    
    
		cout << "virtual Derived1::vfun1()" << endl;
	}

protected:
	int y1;
};

class Derived2 : public virtual Base
{
    
    
public:
	Derived2(int y2) : Base(1), y2(y2) {
    
    }
	virtual ~Derived2() {
    
    }
	virtual void vfun1() override
	{
    
    
		cout << "virtual Derived2::vfun1()" << endl;
	}

protected:
	int y2;
};

class DDerived : public Derived1, public Derived2
{
    
    
public:
	DDerived(int z) : Derived1(11), Derived2(22), z(z) {
    
    }

	virtual void vfun1() override
	{
    
    
		cout << "virtual DDerived::vfun1()" << endl;
	}

	void callX()
	{
    
    
		cout << this->x << endl;
	}

protected:
	int z;
};

3.3最終的な派生クラスDDerivedオブジェクトモデル

ここに画像の説明を挿入
図3-1継承された仮想菱形仮想関数の継承図
ここに画像の説明を挿入
3-2は、仮想菱形の継承の関数ではありません
3-1と図3-2の比較から、仮想継承を見ることができます。一連の架空の菱形仮想関数を使用する継承された最終派生クラスDDerivedオブジェクトモデルは、仮想関数を使用しない仮想継承最終派生クラスDDerivedと基本的に同じです。違いは1つだけです。つまり、基本クラスBaseに追加の仮想ポインターがあります。 、DDerived自体の仮想関数を指します。テーブル。この仮想関数テーブルは、単一継承の仮想関数テーブルと同じであり、D派生自体の仮想関数またはそこから継承された仮想関数を格納します。仮想関数テーブルの定義規則は、最初に基本クラスの仮想関数テーブルの内容をD派生の独自の仮想関数テーブルにコピーし、次にD派生の独自の仮想関数を使用して仮想関数テーブル内の同じ名前の仮想関数を上書きすることです。
同様に、静的メンバー関数、静的メンバー変数、および通常のメンバー関数がある場合、D派生のクラスメモリモデルも影響を受けません。特定のコードブロガーはそれを投稿せず、読者が自分で確認するための小さな割り当てを残します。

4、まとめ

  • ひし形の仮想継承の後、基本クラスのメンバー変数には1つのメモリしかなく、同じメンバー変数が派生クラスにコピーされてメモリを占有することはありません。
  • 仮想継承後、派生クラスは基本クラスのメンバー変数をコピーしませんが、それ自体を指す仮想ポインターを持つ仮想関数テーブルを生成します。仮想関数テーブルは、基本クラスのメンバー変数のオフセットアドレスを格納します。
  • 仮想継承のクラスには仮想関数があります。仮想関数のない仮想継承との違いは1つだけです。つまり、現在のクラスの仮想ポインターが生成され、仮想ポインターは最終的な派生クラスの仮想関数テーブルを指します。仮想関数テーブルには、最終的な派生クラスが格納されます。置換または継承された仮想関数アドレスの後のすべての仮想関数。
  • 非静的メンバーのみがオブジェクトモデルのメモリを占有します。
  • クラスオブジェクトの静的変数と静的関数は、オブジェクトモデルのメモリを占有せず、静的ストレージ領域に格納されます。
  • クラスオブジェクトの通常のメンバー関数は、オブジェクトモデルのメモリを占有せず、通常のデータ領域に格納されます。

5、参照コンテンツ


C ++ C ++オブジェクトモデルとレイアウトのひし形継承問題(3つのクラシッククラスオブジェクトのメモリレイアウト)C ++
でのひし形継承の基本概念とメモリ占有問題
C ++継承(複数継承+複数継承+仮想継承+仮想デストラクタ+再定義)
「C ++オブジェクトモデルの詳細な調査」HouJieページ:83-134

おすすめ

転載: blog.csdn.net/naibozhuan3744/article/details/114192980