技術共有

C: 合成と継承の違い

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

構成の概要と継承との比較

組み合わせとは何ですか
(1) 合成とは、他の複数のクラスのオブジェクトをクラス内のメンバーとして使用することを意味します。
(2) クラスツリーを使ってケースを説明する
(3) 組み合わせもコードの再利用手法であり、その本質は構造含む

#include <iostream>
#include <vector>
#include <string>

// Leaf 类
class Leaf {
public:
    Leaf(const std::string& color) : color_(color) {
        std::cout << "Leaf constructor called: " << color_ << std::endl;
    }

    ~Leaf() {
        std::cout << "Leaf destructor called: " << color_ << std::endl;
    }

    void display() const {
        std::cout << "Leaf color: " << color_ << std::endl;
    }

private:
    std::string color_;
};

// Branch 类
class Branch {
public:
    Branch(int length) : length_(length) {
        std::cout << "Branch constructor called: " << length_ << " cm" << std::endl;
    }

    ~Branch() {
        std::cout << "Branch destructor called: " << length_ << " cm" << std::endl;
    }

    void display() const {
        std::cout << "Branch length: " << length_ << " cm" << std::endl;
    }

private:
    int length_;
};

// Tree 类,包含 Leaf 和 Branch 对象
class Tree {
public:
    Tree(const std::string& leafColor, int branchLength) 
        : leaf_(leafColor), branch_(branchLength) {
        std::cout << "Tree constructor called" << std::endl;
    }

    ~Tree() {
        std::cout << "Tree destructor called" << std::endl;
    }

    void display() const {
        leaf_.display();
        branch_.display();
    }

private:
    Leaf leaf_;
    Branch branch_;
};

int main() {
    Tree tree("Green", 150);
    tree.display();
    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

ここに画像の説明を挿入します

継承と合成の特徴の比較
(1)相続は一種の( a) この関係は推移的であり、対称的ではありません。
(2) その組み合わせは、(がある)の一部の関係であり、
(3) 継承はホワイトボックスの再利用です。クラスの継承により、独自の実装に従って親クラスの実装の詳細をオーバーライドできるため、親クラスの実装は子クラスから見えるようになります。
(4) 継承のホワイトボックス再利用機能は、親クラスの実装詳細をサブクラスに公開するため、クラスのカプセル化機能をある程度破壊します。
(5) この組み合わせはブラックボックスの再利用に属します。含まれるオブジェクトの内部の詳細は外の世界には見えないため、そのカプセル化は比較的良好で、実装における相互依存性は比較的小さいです。
(6) 組み合わせに含まれるクラスは、包含クラスの作成時に作成および破棄されます。組み合わせはブラックボックスの再利用であり、同じタイプの他のオブジェクト参照またはポインターを取得することによって、実行時に動的に定義できます。欠点は、システム内のオブジェクトが多すぎることです。
(7) オブジェクト指向の設計原則は、まず結合してから継承することです。

多重継承とその曖昧さの問題

多重継承
(1) 多重継承とは、サブクラスが複数の親クラスを持つことを意味します。
(2)多重継承のデモ
(3) 多重継承と単一継承の原則には明確な違いはありません。
(4) 多重継承はあいまいさの問題を引き起こす可能性がある
多重継承の曖昧さ 1
(1) シナリオ: C は A と B から多重継承しているため、C 内で A と B の同じ名前のメンバーを呼び出すとあいまいさが生じます。
(2) 理由: C は A と B から同じ名前 (名前空間ドメインが異なる) のメンバーを継承しているため、C のオブジェクトを呼び出した場合、コンパイラはどれを呼び出したいかを判断できません。
(3) 解決策 1: この問題の発生を回避するには、A と B のパブリック メンバーの名前が繰り返し競合しないようにします。しかし、これは制御できない場合もあります。
(4) 解決策 2: コーディング時にどちらを呼び出すかを明確に指定する。 cA::func() を使用して、クラス B の関数の代わりにクラス A の関数を呼び出すことを明確に指定します。
(5) 解決策 3: C の func を再定義すると、C の func が呼び出され、A と B の func が非表示になります。
(6) まとめ:解決はできるが、良い解決策はない。

多重継承の曖昧さ 2
(1) シナリオ: ダイヤモンド相続問題。つまり、A が祖先クラス、B1:A、B2:A、C:B1,B2 になります。このとき、C のオブジェクトを使用して A のメソッドを呼び出すと、あいまいさが生じます。
(2) 分析: c.func() は曖昧で、cA::func() も曖昧ですが、c.B1::func() と c.B2::func() は曖昧ではありません
(3) 解決策: 問題 1 と同じですが、問題 2 はより微妙で、回避するのが困難です。

ここに画像の説明を挿入します

// 祖类
class Aa {
 public:
  void show() { std::cout << "A's method" << std::endl; }
};

// 派生类 B1 和 B2,从 A 继承
class B1 : public Aa {
 public:
  void show() { std::cout << "B1's show" << std::endl; }
  void showB1() { std::cout << "B1's method" << std::endl; }
};

class B2 : public Aa {
 public:
  void show() { std::cout << "B2's show" << std::endl; }
  void showB2() { std::cout << "B2's method" << std::endl; }
};

// 派生类 C,从 B1 和 B2 继承
class C : public B1, public B2 {
 public:
  void showC() { std::cout << "C's method" << std::endl; }
};

int test070103() {
  C c;
  // c.Aa::show();    // 调用 A 的方法 不可以
  c.Aa::B1::show();  // 调用 A 的方法 不可以
  c.B2::Aa::show();  // 调用 A 的方法 不可以
  c.B1::show();      // 调用 B1 的方法
  c.B2::show();      // 调用 B2 的方法
  c.showB1();        // 调用 B1 的方法
  c.showB2();        // 调用 B2 的方法
  c.showC();         // 调用 C 的方法
  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

仮想継承はダイヤモンド継承の曖昧さの問題を解決します

仮想継承の使用方法
(1) シナリオ: ダイヤモンド継承により曖昧性の問題が発生する 本質的に、孫クラス C には B1 と B2 に含まれる 2 つの A オブジェクトがあるため、曖昧性が生じます。
(2) 仮想継承ソリューション: B1 と B2 が A を仮想的に継承すると、C は B1 と B2 を正常に継承できます。
(3) 仮想継承は、ダイヤモンド継承のあいまいさの問題を解決するために作成されています (多態性機能を実現するため)。

仮想継承の実装原理
(1) 仮想継承の原理は、仮想基底クラス テーブル ポインタ vbptr と仮想基底クラス テーブル仮想テーブルです。
(2)参考:https://blog.csdn.net/xiejingfa/article/details/48028491

ここに画像の説明を挿入します

要約する

組み合わせとは何かを理解します。クラス クラスには他のクラス型のメンバー変数が多数あります。
合成と継承の違いを理解する
曖昧さ: 関数を実行する場合、それが指定したい関数であるとは限りません。
解決策: cA::func() を使用して、呼び出されるクラスを明示的に指定します。
仮想継承は条件付きコンパイルに似ています。導入効果を得るために繰り返し導入する必要はありません。

学習記録と違反連絡先は削除されます。
出典: Zhu 先生の IoT 教室