C++面向對象高級編程 part3
@(boolan C++)[C++]
概述
面向對象的三種關系
- composition 組合
- delegation 委托
- inheritance 繼承
組合與繼承
1. composition 組合 has a
template <class T> {
class queue {
...
protected:
deque<T> c;
public:
bool empyt(){ return c.empty()}
size_type size() {return c.size();}
reference front() { return c.front();}
reference back() {return c.back();}
void push(const value_type& x) {c.push_back(x);}
void pop()(c.pop_front();)
}
composition 是has a 的關系。
composition的關系表示法:
Adapter模式
Adapter模式: 新的類類型組合包含已有的類型對象,新的類型的功能完全由已有的類型實現,新的類型是已有類型的功能的簡化(類似 adapter的功能)。
2.composition 關系下的構造和析構
內存結構
構造和析構順序
構造順序由內而外,析構順序由外而內。
- Container首先調用Component的default構造函數,后調用自己的構造函數。
圖中紅色部分為編譯器行為。
- Container首先調用自己的析構函數,后調用Component的析構函數。
注意??:
構造函數的默認行為,編譯器默認在構造函數的初始化列表中調用成員對象的default構造函數。所以在初始化列表中顯式初始化成員對象效率要高于在class body內初始化成員函數(避免了重復初始化的動作)。
3. Delegation/委托。 Composition by reference
delegation 即Composition by reference。
通常不講pointer,僅講reference。
// file string.hpp
class stringrep;
class string {
public:
....
private:
stringrep* rep; // pimpl
};
// string.cpp
#include "string.hpp"
namespace {
class stringrep {
friend class string;
int count;
char* rep;
};
}
Delegation的關系表示法
delegation 雖然能夠訪問某對象,但其成員對象是指向某對象的指針。指針成員指向的對象的創建和析構都不一定由我控制。
delegation中指針成員的生命周期和其所指對象的生命周期不一致。compositon中生命周期一致。
pImpl模式/ (Handle/Body)
p for pointer。
- pImpl將實現的聲明分離,提供了靈活性。這種靈活性來源于delegation中指針成員生命周期與其指向對象的不一致。
string的引用計數和copy on write
引用計數:string中采用引用計數的方式在內容相同對象間共享數據。
copy on write:共享數據的對象間如果有人要改寫自己的數據,則copy共享的數據到新分配的內存(目的是不破壞共享數據)。
copy on write 應用:string 對象間拷貝不會創建新的內存,因為有引用計數機制,僅在copy之后要改寫對象才會創建新內存(copy on write)。
4. Inheritance/ 繼承. Is - a
繼承,委托,組合都是面向對象。
struct _List_node_base{
_List_node_base* _M_next;
_List_node_base* _M_prev;
}
template<typename _Tp>
struct _List_node :public _List_node_base
{
_Tp _M_data;
}
繼承的表示方法
T表示 Template class
public繼承
- public繼承 is-a關系。
- 繼承的價值在于與虛函數搭配。
派生類成員對基類成員的訪問
- 基類的private成員,只有基類和基類的友元可以訪問。
- 基類的public,protected成員,派生列表中使用的訪問標號決定該成員在派生類中的訪問級別
派生列表中的訪問標號
訪問標號僅影響基類的public,potected成員在派生類中的訪問級別。
- public繼承:基類成員在派生類中的訪問級別保持不變。
- prtoceted繼承: 基類的public,protected成員在派生類中為protected成員。
- private繼承:基類的public,protected成員在派生類中為private成員。
::無論以何種方式繼承,派生類對基類成員的訪問權限一致,繼承類型僅影響派生類用戶對基類成員的訪問級別。::
Inheritance關系下的構造和析構
內存模型
構造和析構順序
構造由內而外, 析構由外而內。類似于compositon的順序。
Derived 的構造函數,先調用base的default構造函數,后調用自己的構造函數。base的default構造函數在derived的構造初始化列表中被默認調用
Derived::Dervied(…): Base() {};
Derived的析構函數,先執行自己的析構函數,后調用base的析構函數。
Derived::~Derived(…) {… ~Base()};
base的析構函數必須是virtual
如果多態基類的析構函數是non-virtual的,會造成“局部對象銷毀”,僅銷毀了基類的對象。
虛函數與多態
1. derived class 繼承了 base class 的哪些東西?
- 內存數據
- 函數的調用權
derived class 繼承了base class 的函數調用權,所以 derived class 可以調用base class的函數。
2. Inheritance with virtual function
注意??:
virutal函數的設計取決于derived class 是否想要重新定義(override/ 覆蓋)base class的已有定義。這里要區分重載(overload)和覆蓋(override)。
virtual function
class Shape {
public:
virutal void draw() const = 0; // pure virtual
virtual void error(const std::string& msg); // impure virtual
int objectID() const; // non-virtual
- non-virtual : 不希望derived class 重新定義(override / 覆蓋)它(base class function member)。
- virtual: 希望derived class重新定義它,且它已有默認定義。
- pure virtual:希望derived class一定要重新定義它,且它沒有默認定義。
理解???♂?:
- 虛函數的聲明在base class中指定, 控制derived class對接口的繼承能力。
- 虛函數使derived class繼承了base class 接口的同時,讓dervied class具有進化該接口行為的能力。
注意??:
- 不能創建具有純虛函數類型的對象。
- 繼承于純虛類的dervied class 中具有純虛類對象。
template method
class CDocument {
public:
virutal Serialize(){};
OnFileOpen() { // template method
...
Serialize();
}
}
class CMyDocument : public CDocument {
virtual Serialize() {....}
}
....
int main () {
CMyDocument doc;
doc.OnFileOpen();
}
OnFileOpen就是template method;
template method:
template method的做法將已實現base類型的部分功能,延緩實現,將其交由derived class 實現。
將Application Framework框架和Application實現分離。
理解
- template method即 ,在其實現中調用base class virtual function的base class non-virtual function 。
- 好處:
derived class 可以復用base class 中non-virtual function實現中通用的框架/流程/接口,但針對不同derived class object的調用 non-virtual function 的行為略有差異(差異在non-virtual function中調用virtual function)。
virtual function調用過程
注意下圖中this指針的作用。
3. Inheritance + Composition 關系下的構造和析構
內存模型/UML關系
構造由內而外,析構由外而內
Derived 的構造函數首先調用base的default 構造函數,
然后調用Component的default構造函數,
最后調用自己的構造函數。
Derived::Derived(...) : Base(),Component() {...};
Derived 首先調用自己的析構函數,
然后調用Component的析構函數,
最后調用base的析構函數
Derived::~Derived(){... ~Componet(),~Base()};
委托+繼承設計
設計思想:用composition(組合)/delegation(委托)/inheritance(繼承)三個工具,去設計解決現實問題的方法。
理解 1:委托應用于設計的靈活性在于對象創建的靈活性,delegation class a對象可以通過指針的方式間接擁有某對象,該被擁有的對象創建方式可以很動態。
理解 2: 委托+繼承強化了委托的應用,鑒于base指針可以指向derived class對象。
0. Obeserver
class Subject {
int m_value;
vector<Observer*> m_views;
public:
void attach(Observer* obs) {
m_views.push_back(obs);
}
void set_value(int value) {
m_value = value;
notify();
}
void notify() {
for(int i = 0; i < m_views.size(); ++i)
m_views[i]->update(this, m_value);
}
}
class Observer {
public:
virtual void update(Subject* sub, int value) = 0;
}
UML關系圖
1. 類似文件系統的問題解決
文件系統問題?
如何設計目錄的數據結構?目錄中既有文件類型,又包含目錄類型。
用composite設計模式/ delegation + inheritance 解決問題
- composite解決的問題?用一種結構能夠同時保存多種不同類型的數據。
- base類指針數組,解決上述問題,注意必須是指針數組,因為指針的大小固定。
example code
class Primitive : public Component {
public:
Primitive(int val): Component(val){}
};
class Component {
int value;
public:
Component(int val) {value = val;}
virutal void add(Component*) {}
};
class Composite: public Component {
vector<Component*>c;
public:
Composite(int val):Component(val) {}
void add(Componet* elem) {
c.push_back(elem);
}
}
2. Prototype
UML
- 靜態成員的表示方法,在成員的名稱下面添加下劃線
- 數據成員的表示方法,成員名稱在前,類型在后。
Prototype要解決的問題
- 在base類種如何創建未來才會設計的類型對象。在不知道對象類型的前提下創建對象。
- 主要發生在框架設計中,框架設計者不知道未來使用者的類型。
注意??:
- prototype中,是在dervied類自己創建對象,不是使用者創建dervied對象,所以dervied類中包含一個static derived類對象。
- derived類自己創建對象并注冊到base類中。
- prototype中,base類中創建derived類對象,而不是base類/dervied類對象中的base對象