本文是我學習C++沉思錄第6章的筆記
本文主要講述了Handle類的概念,定義方法以及寫時復制技術。
?
在前文(Surrogate代理類)的講解中我們了解到了代理的實現方法.
代理類有很多好處,但是麻煩的是每次都得進行復制.如果該類是經常使用并且member很多的話,這樣復制的消耗是十分客觀的.
因此這里就要介紹另外一種代理類,Handle,也就是句柄類.
?
為何使用句柄類?
首先就是復制問題.前面有談到,有些類內部數據很多,采用復制消耗非常大,這種情況下就必須采用句柄類來進行操作.
其次是由于函數的參數和返回值都是采用了復制進行自動傳遞.雖然c++中引用可以避免,但是很多情況下返回值采用引用并不明智.
對于采用指針的方式,可以解決問題,但是又會引入調用者對于動態管理內存的麻煩.而這往往是很多錯誤的根源.
?
何為句柄類呢?
句柄類可以理解為采用了引用計數的代理類.
其多個句柄共享了同一個被代理的類.通過引用計數的方式來減少復制以及內存管理.
其行為類似指針,因此也有智能指針之稱,但其實差別很大.后面會有講述.
?
句柄類例子:
先有一個簡單的類Point
1 class Point
2 {/*{{{*/
3 public:
4 Point():_x(0),_y(0){}
5 Point(int x,int y):_x(x),_y(y){}
6 int x()const {return _x;}
7 void x(int xv) { _x = xv;}
8 int y()const { return _y;}
9 void y(int yv) { _y = yv;}
10 private:
11 int _x;
12 int _y;
13 };/*}}}*/
接下來我們要定義其的Handle類.
我們的Handle類:
1 class Handle
2 {
3 public:
4 Handle():up(new UPoint){}
5 Handle(int x,int y):up(new UPoint(x,y)){}
6 Handle(const Point&p):up(new UPoint(p)){}
7 Handle(const Handle &h);
8 ~Handle();
9 Handle& operator=(const Handle &h);
10 int x() const{ return up->p.x(); }
11 int y() const{ return up->p.y(); }
12 Handle& x(int);
13 Handle& y(int);
14
15
16 private:
17 UPoint *up;
18 void allocup();
19 };
這里說明我們的Handle和指針的不同之處.
也許有讀者會對Handle有疑問,為什么不采用operator->來直接操作point呢?
其實顧慮就是operator->返回的是point的地址.也就是使用者可以輕易的獲得point的地址進行操作,這并不是我們想要的.這也就是Handle也pointer不想同的地方.
UPoint是為了采用引用計數定義的數據結構
1 //all member is private..only assess by Handle
2 class UPoint
3 {/*{{{*/
4 friend class Handle;
5
6 Point p;
7 int u;//count
8
9 UPoint():u(0){}
10 UPoint(const Point&pv):p(pv){}
11 UPoint(int x,int y):p(x,y),u(1){}
12 UPoint(const UPoint &up):p(up.p),u(1){}
13 };/*}}}*/
?
對于Handle類的操作,我們要在Handle類進行復制的時候,累加Handle指向的UPoint的計數值
即復制構造函數以及賦值函數
1 Handle::Handle(const Handle &h)
2 :up(h.up)
3 {
4 ++up->u;
5 }
6
7 Handle& Handle::operator=(const Handle &h)
8 {
9 ++h.up->u;
10 if (--up->u == 0)
11 delete up;
12 up = h.up;
13 return *this;
14 }
而對于析構函數,則是減小引用計數,如果減到0了,就說明沒有其他的Handle指向了UPoint,因此我們要刪除掉.
1 Handle::~Handle()
2 {
3 if (--up->u == 0)
4 delete up;
5 }
剩下的就是定義Handle對于Point的操作了.即Handle::x(int xv)和Handle::(int yv)了.
這里有2種寫法.
一種是像指針一樣,對于賦值,就直接修改指向的Point里面的值.這種方法有一個問題,即所以都指向這個Point的Handle類獲取的x值都會變化.
代碼:
1 //point like
2 Handle& Handle::x(int xv)
3 {
4 up->p.x(xv);
5 return *this;
6 }
7 //point like
8 Handle& Handle::y(int yv)
9 {
10 up->p.y(yv);
11 return *this;
12 }
?
還有一種是寫時復制技術,即每次對于共享的Point進行修改的時候都復制一份新的Point,然后進行修改.
這種技術在Handle中大量采用.在stl中,string也采用了同樣的方法.
其額外開銷很小,而效率也不差.
代碼:
1 void Handle::allocup()
2 {
3 if (up->u != 1)
4 {
5 --up->u;
6 up = new UPoint(up->p);
7 }
8 }
9
10 Handle& Handle::x(int xv)
11 {
12 allocup();
13 up->p.x(xv);
14 return *this;
15 }
16
17 Handle& Handle::y(int yv)
18 {
19 allocup();
20 up->p.y(yv);
21 return *this;
22 }
至此,Handle類的第一部分就講完了.
之后會有第二部分的講解.解決了多出了一個UPoint的麻煩.