C++Handle(ハンドル)part 1

11471 ワード

本文は私がC++沈思録第6章を勉強したノートです.
本文は主にHandleクラスの概念,定義方法および書き込み時レプリケーション技術について述べた.
 
前述(Surrogateエージェントクラス)の説明でエージェントの実現方法を理解した.
エージェントクラスには多くのメリットがありますが、面倒なのは毎回コピーしなければなりません.このクラスが頻繁に使用するメンバーが多い場合、このようなコピーの消費は客観的である.
そこでここでは,もう一つのエージェントクラス,Handle,すなわちハンドルクラスを紹介する.
 
なぜハンドルクラスを使用しますか?
まずレプリケーションの問題です.前述のように、一部のクラスの内部データが多く、採用レプリケーションの消費量が非常に大きい場合、ハンドルクラスを採用して操作しなければならない.
次に、関数のパラメータと戻り値はいずれもコピーによる自動伝達である.c++での参照は避けることができるが、戻り値が参照を採用するのは賢明ではない場合が多い.
ポインタを用いる方式では問題を解決できるが、呼び出し者がメモリを動的に管理する面倒を導入する.これは往々にして多くの間違いの根源である.
 
ハンドルクラスとは?
ハンドルクラスは、参照カウントを採用するエージェントクラスと理解できる.
複数のハンドルが同じエージェントのクラスを共有する.カウントを参照することにより、レプリケーションおよびメモリ管理を低減する.
その行為はポインタに似ているため、スマートポインタと呼ばれることもあるが、実は大きな違いがある.後でお話しします.
 
ハンドルクラスの例:
まず簡単なクラスポイントがあります
 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のアドレスを返すことだ.すなわち、利用者が簡単にポイントのアドレスを取得して操作できることは、我々が望むものではない.これが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つの書き方があります.
1つは、ポインタのように、付与値に対して、指向する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 }

 
もう1つは、共有する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類の第1部は終わりました.
後で第2部の説明があります.UPointのトラブルを解決しました.