yamicha.com's Blog - Presented by yamicha.com
Blog yamicha.com's Blog - 2018/07 の記事
[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]

yamicha.com's Blog
 諸事情により、現在更新休止中。ご了承ください。もし今後ブログを再開することがあるとすれば、その際にはこのブログスクリプトではなく、新しく開発したものによるかもしれません。
 当ブログ管理者についてはこちらをご参照。
開発魔法(737)
社会問題(733)
お知らせ(11)
質問・バトン回答(15)
ゲスト出演(8)
経済・知的財産(150)
ゲーム開発(182)
[Ada] 勝手に補足
- Note
- 金配りの次の一手


- Endless Ultimate Diary
- 銃世界

漢字バトン
- うるる雑記帳
- 漢字接力棒

ツキアイゲノムバトン
- ブログ@うにうに画像倉庫
- あぶ内閣

縺イ縺セ縺、縺カ縺励ヰ繝医Φ
- 月夜のボヤキ
- 騎士サーラバトン
パスワードを使う
名無し (2012/02/27)


開発者解放裁判
yamicha.com (2010/03/14)
Winnyに関しては、私も「純白」とまでは考えておりませんし、使用し..

開発者解放裁判
通りすがり (2010/03/08)
winnyに関しては「ダウンロードソフト板」なんてところを拠点に開発..

新型インフルエンザの恐怖
いげ太 (2009/11/03)
> C#などの「int Some(int , int)」は、F#では「(int * int) ->..

時効に関する思考
yamicha.com (2009/08/31)
>いげ太さんコメントありがとうございます。手元にドキュメントが少..
Homepage
Blog Top
Normal View
List View
Search
Calendar
Comment List
Trackback List
Blog RSS
Send Trackback
Phone Mode
Administrator
yamicha.com
Blog
るううるる。
Source
法令データ提供システム
FindLaw
Development
Java2 Platform SE 6
Java EE 6 API
MySQL Developer Zone
PHP Reference
MSDN Library
Ada Reference Manual
Objective Caml
Python Documentation
Erlang
Prolog Documents
平成十三年九月(略)措置法期限切れ新法の問題
2007/10/14(Sun)22:20:26
 混迷のテロ対策特別措置法。言い訳をつらねたこのとてつもなく長い名前の法律が期限切れとなり、与党は新法を検討しています。新法は有効期限を2年とし、給油や給水の活動に限るものとしています。また、閣議による事前承認は不要となり、事後報告で済むようになります。
 対する民主党・小沢代表はこの新法に反対しているようですが、氏の主張がまた物議をかもしています。小沢氏の主張は「武力を伴うものであっても、国連の平和活動に参加することは憲法上許される」というものであり、政権を取ったらアフガニスタンのISAF(国際治安支援部隊)に参加したいと述べた他、PKOにも参加する考えのようです。
 すると今度は与党から「小沢氏の案は憲法上許されない」。他者を批判するのは結構ですが、自分らが今までやってきたこと、特にイラク特措法に関してはどう考えているのでしょう。憲法を迂回する小細工として「戦闘地域」「非戦闘地域」を定めているのですが、マスコミの中には「イラクは危険である。だから自衛隊を派遣するしかない」などという法律の中身を完全無視した主張をする者もおり、ひとしきり笑わせていただきました。ともかく、テロ対策特措法もイラク特措法も違憲の疑いが強いことは事実です。
 私の個人的見解を述べれば、集団的自衛権の行使、及び集団的自衛権に当たるような自衛隊派遣、ISAFやPKOへの参加、その他イラクなど外国での活動は、絶対的に禁じられるものではないと考えています。ただし、いずれも憲法違反である、または憲法に違反する可能性が極めて高いため、これらのことをどうしても行いたいのであれば、憲法改正を発議し、衆参両院で2/3以上の賛成によって国民投票に送り、国民の過半数の賛成によって改正した上で行われなければなりません。それができないのであれば、これらの案は検討にも値しません。
 特に憲法解釈の変更で集団的自衛権の行使を認めるなどの行為は愚の骨頂です。集団的自衛権の行使が必要なのであれば、憲法解釈ではなく憲法の改正によらなければなりません。正々堂々と集団的自衛権を明記した憲法を提出し、衆参両院の賛成の上で国民投票にかけ、それで成立が決まったのであれば、集団的自衛権の行使が可能になります。
 ただ、憲法改正にも懸念があります。安倍内閣が強引極まりない手法で自己都合の国民投票法を作ってしまったためです。戦後に新しい憲法が制定された際、憲法改正には国民投票が必要であることは明示されましたが、それはあくまで国民投票の必要性を明示しただけに過ぎず、国民投票の根拠となる法律のインプリメントは日本に投げられました。それから60年、国民投票法は作られずに来ましたが、安倍内閣でついに作成されるに至りました。
 なお、安倍内閣については「国民投票法、教育改革、公務員改革など従来の政権が手をつけなかったことをやってのけた」とする意見がごく一部に存在しますが、これは誤りです。戦後のほとんどの時期は自民党が衆参両院を支配しており、いつどこでどの首相が同様の法案を提出しても成立させることはできたのです。しかし、国民のため、選挙のため、議論が煮詰まっていないため、主義に反するためなど、様々な理由によるのでしょうが、多くの首相はあえてこのような乱暴なことを避けてきました。ところが、安倍内閣はそれをやりました。結果、参院選で醜態をさらすに至りました。国民投票法を成立させた理由としては、自分の内閣で(同法の凍結期間が切れる)3年後に憲法改正を行おうという目論見があったのでしょうが、それもかなわぬ夢となりました。つくづく皮肉なものです。
 可能なら今からでも構いませんので、最低でも「無効票を含めて過半数の賛成が必要」とすべきですし、できれば「投票率が50%に満たなければ却下」などの最低投票率規定も盛り込むべきです。本当は「全有権者のうち過半数の賛成が必要」としたいところですが、国政選挙の投票率50%の現状では可決の可能性がほぼゼロに等しくなるため、前2つの案までが妥当でしょう。現状の国民投票法は「可決ありき」の極めてぞんざいなものです。
 ともかく、集団的自衛権の行使を認めるにせよ、PKOに参加するにせよ、これは憲法改正によって行われるべきことです。もしこれらを次期衆院選の公約として掲げたいのであれば、それに伴う憲法改正もセットとして掲げなければなりません。政府の勝手な自己都合で「昨日までは憲法違反につき集団的自衛権の行使はできませんでしたが、今日からできるようになりました」では困るのです。

 自殺サイトの殺人幇助問題により、さらにサイトへの締め付けが厳しくなりそうです。
 確かにWebサイトを用いた犯罪は許しがたいものです。強盗殺人や殺人幇助など、見過ごせない犯罪が発生していることは事実です。コンピュータ技術を詐欺の道具に利用した堀江氏ともども、私はこのような犯罪者を許すことができません。しかし、こうした問題を締め付けによって解決することはできません
 もしそれでもサイト規制がやむを得ないというなら、例えば「円天」などの諸問題についてはどうでしょう。あれだけ幅広い層に多くの被害をもたらした裏には、タレントを使った宣伝や口コミの他、紙または映像媒体の媒介があったと考えるべきです。仮にそうなら紙や映像媒体が詐欺に加担していたことになりますから、こちらの規制も徹底的に行うのでなければ犯罪を防ぐことはできません。無論、Webサイト規制を訴えていらっしゃるマスコミ諸君は「犯罪抑止のためならやむを得ない」としてこれに従わなくてはなりません。
 また、違法なサイトを規制するにしても、犯罪や自殺幇助サイトは規制すべきですが、広義の自殺サイトは規制すべきではありません。自殺サイトには自殺を思いとどまらせる効果も高いものと考えられ、規制は時として自殺推進になりかねません。犯罪サイトについても、ちまたには犯罪や事件を取り扱った様々なサイトが存在しますが、それらの多くは資料としての価値を持っており、規制するなら違法行為を実際に行うサイトのみに限定されなければなりません。
 しかし、政府やマスコミにとってWebサイトの台頭が愉快なものであるとは到底考えられません。本日は「新聞週間」とかで、いくつかのマスコミが新聞の信頼性を誇張して論じていますが、新聞には昔から信頼性などなく、読み比べができない時代に「信頼できる」とみなされていたに過ぎません。その過去の遺産によって未だに世論調査では信頼できるメディアとみなされますが、この比率が減ることはあっても増えることはありません。新聞社にとってこの状況が面白いわけがなく、一部コラムには露骨にインターネットを攻撃するようなものもみられました。
 したがって、政府が規制を推進し、マスコミはその歯止めにならず、あるいは規制を後押しすることさえ行い、インターネットが意味もなく規制される恐れは十分考えられます。それでなくても総務省はインターネット規制の方針を打ち出しているのです。さらに残念なことに、マスコミは利権を使って政府に圧力をかけることができますが、インターネットは利権にもしがらみにも属さない存在であるため、政府に圧力をかけることができません。
 インターネット規制の動きを舛添氏流に言えば次のようになるでしょうか。「Webサイトは犯罪を行っている」「Webサイトは信頼ならない」。これに対して一般サイトが抗議すれば「小人のざれ言に付き合う暇はない。そんな暇があるなら私はもっと大事なことをしなければならない。そういうざれ言は犯罪サイトに言えばいい」。本当にこのような理屈でサイト規制がなされたのではたまったものではありません。

 俗に「フラグ(〜を立てる、〜が立つ)」という言葉があります。フィクションなど一部の分野では「死にフラグが立つ」(「登場人物が後に死亡する伏線が張られた」のような意味)などと称されたりしますが、これはもともと開発分野の用語です。使い方はといいますと、例えば次のようなプログラムでは、
public class Flag{
	boolean flag = false;
	public void method(){
		if(flag)
			return;
		// 何らかの処理
		flag = true;
	}
}
 method()に書かれた1回目の処理を行った時点でフラグが立ち、これを2度以上呼び出しても何も起こらなくなります。
 そしてここからが重要な点なのですが、フラグは論理演算と密接に関係しています。フラグ自体が大抵はboolean型、あるいはboolean型を結果とする演算によって処理されるものであるため、ある意味フラグ自体が論理演算なのです。数学的には「数学A」の分野らしいのですが、私がそれを未修得であるため、以下はすべてプログラム的な解説になります。
 まず、プログラム上における条件判定は、ほぼ必ずboolean型によって行われます。boolean型はYESかNO、真か偽、trueかfalseの2つに1つであり、あいまいさはありません。Javaでは条件判定は必ずboolean型であり、それ以外のものを使って条件判定を行おうとすればエラーになってしまうのですが、C/C++やPerlなどではbooleanでないものを用いた条件判定も可能です。ただし、例えばCの場合では「判定する値が0の場合はfalse、それ以外ならtrue」とみなされるのであり、突き詰めれば「値 != 0」というboolean型の演算がなされているものと考えることができます。
 プログラムを打つ際には当然として、開発に限らずあらゆる分野において論理演算は極めて重要です。習得するのとしないのでは、様々な物事に対する理解度が天と地ほど変わってきます。開発を始めて最初に覚えなければならないのがこれですが、本当は開発など関係ないという方にこそぜひ習得していただきたいものです。
 それでは例題を。人の好みや交友関係は色々ですが、少なくとも私は「頭が良い人」が好きです。頭の良い友人に恵まれるほど幸せなことはありません。「頭が良い」の基準は色々であり、一概に評価できるものではありませんが、それを突き詰めるのは本題ではありませんので、ここでは「頭脳明晰」か「それ以外」かのどちらかで評価できるものとします。その場合の演算はこうなります(相手が好みならtrue、そうでないならfalse)。
// 単一の演算(これ単体だけで true または false)
頭脳明晰

// 否定形の演算
!頭脳明晰以外
 条件に感嘆符(!)を付加すると、得られる結果が反転します。言語によっては「NOT 条件」と書いたりもします。
 しかし、これでは何をもって「頭脳明晰」とするのか不明です。無論、通常のプログラムでもこれでは使い物になりません。そこで、例えばこの条件を「私より頭が良い」というものにしてみます。頭の良さはテストの点などで測れるものではなく、数値化することなど絶対にできませんが、例によってここでそれを論じても仕方がないため、頭の良さは数値であるものとします。
私の頭脳 < 相手の頭脳
 これの結果はtrueまたはfalseですから、すなわちこの演算がboolean型を返すことになります。これがプログラムの条件判定の基礎です。
 しかし、当然のことではありますが、頭脳明晰だからといって他者の迷惑を顧みずに詐欺を行うなどする連中、明らかに性格の悪い連中などを友人にできるわけがありませんし、頭脳面では自分と同等か少々鈍っていても、尊敬に値する人物は存在するでしょう。私を頭脳面で下回る人がそうそう存在するかは別として。
 相手が頭脳明晰であろうとも詐欺師ならお断りであり、相手が頭脳明晰でなくても向上心があれば頭など問題にならないとするなら、以下のような式が作れます。
頭脳明晰 && !詐欺師
頭脳明晰 || 向上心
 &&はAND演算と呼ばれ、言語によっては「AND」で表記します。これで連結した条件同士は「両方がtrueである限りtrue」となり、片方または両方がfalseならfalseとなります。対して||はOR演算と呼ばれ、言語によっては「OR」で表記します。これで連結した条件同士は「どちらかがtrueであればtrue」となり、両方がfalseである場合のみfalseとなります。
 これらのもう1つ(プログラム的に)重要な点としては、例えば(条件A && 条件B)あるいは(条件A || 条件B)のような場合、前者では条件Aがfalseであれば条件Bがtrueでもfalseでも結果はfalse以外にはなりませんので、条件Aがfalseならその時点で評価を打ち切ります。後者であれば、条件Aがtrueなら条件Bがfalseでも必ずtrueになりますので、条件Aがtrueならその時点で評価を打ち切ります。
 このような性質から、プログラムでは次のような処理を見かけることがあります。
エラーが出たらfalse、成功ならtrueを返す処理 || エラーメッセージ表示処理
 最初の条件は正常に終了すればtrueを返しますので、OR演算はその時点で打ち切りとなります。しかし、仮にエラーが出てfalseが返った場合、OR演算を続けることになりますので、エラーメッセージ表示処理が実行され、エラーメッセージが表示されます。
 このAND及びOR演算が何に使えるかといいますと、法律の理解をすさまじいまでに補助してくれる他、その他様々な学問の理解をサポートしてくれます。大変役に立ちますので、未修得の方は習得することを強くおすすめします。
 例えば殺人罪は(かなり簡略化していますが)次のように規定されています。「他人を殺害した者は懲役5年以上、無期懲役、または死刑に処する」「この罪の未遂は罰する」。これを条件式にすればこうなります。
殺人既遂 || 殺人未遂
 既遂なら後の条件を検討せずとも殺人罪が適用されますし、未遂であれば後の条件から殺人罪が適用されます。
 また、強盗の罪を例にするなら、強盗罪の適用条件として「金品を盗む」「暴力などでの威嚇、脅迫行為」が存在しますので、以下のような条件となります。
窃盗行為 && 暴力などでの威嚇行為
 条件判定には「無駄な判定」も存在します。このような判定はコードの可読性を下げ、処理にオーバーヘッドをもたらすため、なるべくなら避けるべきとされています。例えば次のような条件は、現行の日本国法律上では全く無意味です。「15歳以上または成人の方に限らせていただきます」。言うまでもなく、15歳未満の成人はいません。したがって、これは無駄な判定が混ざっていることになります。
 条件判定は基本的に式ですので、カッコで優先順位を指定して重ねることができます。例えば次のような場合を考えてみましょう。「この企画の参加には、Servlet/JSPの知識を有すること、またはHTMLの知識があってJavaが使えることが必要です」。この場合、Servlet/JSPの知識があれば他の知識は不問であり、Servlet/JSPの知識がなくても後の条件を満たせば良いのですから、
Servlet/JSPの知識 || (HTML知識 && Javaの知識)
 このような式が作れます。
 もう1つ、あまり使わないのではありますが、条件判定にはXORなるものも存在します。これは「一方がtrueで他方がfalse」の場合にのみtrueを返すものです。言語によって「^^」と書いたり「XOR」と書いたりします。例えばコーヒー党と紅茶党のいずれか片方でなければならず、どちらも好き、またはどちらも嫌いではいけない場合は
コーヒー ^^ 紅茶
 こうなります。しかし、この判定には無理にXORを使う必要はなく、
コーヒー != 紅茶
 で同じ条件が作れたりします。
 その他、SQLなどtrueを1、falseを0として数値化して扱う言語でのみ使える手法に「条件判定の足し算」があります。普通は使いませんが、場合によっては結構楽です。
// 3条件のうち2つを満たす場合
条件A + 条件B + 条件C = 2
 以前にSQL編で使った強引極まりないテクニックではありますが、SQLではCやJavaなどと違って一時変数にデータを格納しておいて演算するといったことが困難であるため、クエリ一本で対話しようという場合にはあの手この手です。

 条件判定についてはこの程度にして、本日はさらにテンプレートの続編です。今回は「スマートポインタ」と呼ばれるポインタがテーマです。
 スマートポインタは、C++でポインタに使われる演算子(->、*など)がオーバーロードできる点、及びテンプレートの型可変性を上手く用いた技術で、ポインタを扱うかのようにラップ型を扱うことができるというものです。主なメリットはポインタ管理を自動化することができる点にあり、破棄されたポインタへのアクセスやメモリリークを防止することができます。
 スマートポインタ(と呼ばれるもの)の作成自体はさほど難しくありません。適当なテンプレートクラス<class T>を宣言し、T*型を保持するメンバ変数を作り、T& operator *とT* operator ->さえオーバーロードしてメンバ変数を返すようにすれば、テンプレートクラスをポインタのように扱えるのです。
 当然、変数をポインタと同等に扱うのであれば、ClassName<T>同士の代入も可能なはずですから、operator =(ClassName<T>&)のオーバーロードも必要になります。また、変数宣言直後の代入(ClassName<Type> var = ...;)はoperator =をオーバーロードしても行えませんので、コンストラクタ(ClassName(ClassName<T>&))を宣言することで行います。宣言直後の代入には(なぜか)このコンストラクタが呼ばれるようになっています。
 後は、例えば参照カウント型ガベージコレクトを実装したいのであれば、operator =(T*)及びClassName(T*)もオーバーロードします。これを以下のような記述で使用できるようにすれば、テンプレートクラスをポインタであるかのように用いることができます。
class Type{
	int value;
public:
	Type(int v){
		value = v;
	}
	int GetValue(){
		return value;
	}
	void SetValue(int v){
		value = v;
	}
};

// 以下、実装内容(それぞれの場面で呼ばれる関数をコメントで示す)
ClassName<Type> var = new Type(10);	// constructor(T*)
var->SetValue(100);	// T* operator ->()
std::cout << var->GetValue() << std::endl;	// T* operator ->()

ClassName<Type> var2 = var;	// constructor(ClassName<T>)
var2 = new Type(20);	// operator =(T*)

var = var2;	// operator =(ClassName<T>)

std::cout << (*var).GetValue() << std::endl;	// T& operator *()

ClassName<int> var3 = new int;	// constructor(T*)
*var3 = 200;	// T& operator *()

// 参照されなくなったデータは参照カウントを使って ClassName 内で破棄
 これがスマートポインタと呼ばれるものです。以下に私が実装した参照カウント付きスマートポインタを示します。
template<class T> class Reference{
	int count;
	T *value;

public:
	Reference(T*);
	Reference<T>& operator *();
	void operator ++(int);
	void operator --(int);
	T* GetValue();
};
template<class T> Reference<T>::Reference(T *v){
	count = 1;
	value = v;
}
template<class T> Reference<T>& Reference<T>::operator *(){
	return *this;
}
template<class T> void Reference<T>::operator ++(int){
	count ++;
}
template<class T> void Reference<T>::operator --(int){
	count --;

	if(count <= 0){
		delete value;
		delete this;
	}
};
template<class T> T* Reference<T>::GetValue(){
	return value;
}

template<class T> class Pointer{
	typedef Reference<T> R;

	R *ref;

	R* GetReference(){
		return ref;
	}
public:
	Pointer(){
		ref = NULL;
	}
	Pointer(T *p){
		ref = new Reference<T>(p);
	}
	Pointer(Pointer<T> &p){
		ref = p.GetReference();
		(*ref) ++;	// インクリメント
	}
	~Pointer(){
		if(ref != NULL)
			(*ref) --;
	}
	Pointer& operator =(T *p){
		// すでに ref にデータが存在するならデクリメント
		if(ref != NULL)
			(*ref) --;

		// 新しい Reference を確保
		ref = new Reference<T>(p);

		return *this;
	}
	Pointer& operator =(Pointer<T> &p){
		// すでに ref にデータが存在するなら後でデクリメント
		Reference<T> *old = ref;

		// Reference を取得
		Reference<T> *r = p.GetReference();
		(*r) ++;	// インクリメント

		ref = r;

		if(old != NULL)
			(*old) --;
		return *this;
	}
	T& operator *(){
		return *(ref->GetValue());
	}
	T* operator ->(){
		return ref->GetValue();
	}
};
 このクラスは、例えば以下のように用いることができます。
class Base{
public:
	void call(){
		std::cout << "base" << std::endl;
	}
};

void main(){
	Pointer<Base> p = new Base();
	Pointer<Base> q = p;
	q->call();
	p = p;
	p->call();
	{
		Pointer<Base> q = new Base();
		Pointer<Base> r = q;
		r = p;
		(*q).call();
	}
}
 扱いはほとんど通常のポインタと変わらない上、参照カウントで自動的にガベージコレクトを行ってくれるため、メモリを管理する必要もありません。逆に言えば、不整合を避けるためにユーザーにはメモリを直接触らせないようになっています。勝手にメモリの内容を取得された挙句にdeleteされてはたまったものではありません。つまり、スマートポインタはポインタをカプセル化するものであるといえます。
 原理としては、ポインタに用いられる*や->をオーバーロードすることでポインタ同様の操作性を確保している他、=によって新しいPointer<T>またはT*が代入された際には古いオブジェクトの参照カウントを減らし、同時に新しいオブジェクトのカウントを増やし、スコープなどによってオブジェクトが破棄された際にも参照カウントを減らし、カウントがゼロになったらオブジェクトを破棄することで、メモリの自動管理を行っています。
 ここでオーバーロードにかかわる注意点を1つ。演算子には既存の動作(=なら代入など)が定義されており、それに別の動作を付加するから「演算子のオーバーロード」と呼ばれているのでしょう。したがって、既存のオペレーションをオーバーロードしなかった場合、既存のオペレーションが用いられることになります。
 どういう意味かといいますと、自分自身のクラスの参照型を取るoperator =をオーバーロードしなければ、=はオブジェクト同士の代入(コピー)とみなされてしまうのです。また、前述の通りオブジェクトの宣言直後の代入のみは通常の代入と意味が異なり、
Pointer<Type> p = new Type(100);

// 1
Pointer<Type> a = p;

// 2
Pointer<Type> a;
a = p;
 この1と2では呼び出される関数が変わります。具体的には、1の場合はコンストラクタ(Pointer<T>&)が呼び出され、2ではoperator =(Pointer<T>&)が呼び出されるのです。もし1の場合にコンストラクタ(Pointer<T>&)が定義されていなければ、コンパイラはこれをオブジェクトのコピーとして扱い、もちろんoperator =は呼ばれません。
 つまり、何が言いたいのか。operator =を定義したことに安心して、うっかり自分自身の参照型を取るコンストラクタを作り忘れて1のような記述を行うと、operator =が呼ばれるのではなく自分自身のコピーが生成されてしまうことを絶対に忘れないようにしましょう。これが例えばint型など無関係の型を取るのであれば、operator =にせよコンストラクタにせよ定義忘れにはエラーを出してくれるのですが、自分自身の参照型を取るoperator =やコンストラクタに関してはデフォルトの動作(コピー)が存在しているため、エラーを出してくれないのです。ここを間違えると大変な目に合います。
 それからもう1つ。先の参照カウント型スマートポインタには重大な欠陥があります。無効なアドレスにアクセスしてエラーを出すようなタイプの欠陥ではなく、言語仕様上の欠陥です。
 C++では子クラス型のオブジェクトを親クラス型に代入することが許されています。通常のオブジェクトに対してこれを行うと、子クラスのオブジェクトは親クラスのオブジェクトに変化してしまうのですが、ポインタであればそれを避けることができます。そしてスマートポインタはポインタと同等に扱うものです。ところが、以下の記述は動作しないのです。
class Base{
public:
	void call(){
		std::cout << "base" << std::endl;
	}
};
class Child : public Base{
public:
	void call(){
		std::cout << "child" << std::endl;
	}
};

void main(){
	// 普通のポインタなら動作するのに...
	Child *c = new Child();
	Base *b = c;
	delete c;

	// こちらでは動作しない
	Pointer<Child> p = new Child();
	Pointer<Base> q = p;
}
 なぜなら、C++のコンパイラはコンパイル時にPointer<Child>とPointer<Base>の2つのクラスを生成するのですが、この両者には何の関係もないためです。全く別の型なのですから、代入互換になっているわけがありません。例えばPointer<Base>のoperator =(Pointer<T>&)はoperator =(Pointer<Base>&)に、コンストラクタも(Pointer<Base>&)に変換されているわけですから、Pointer<Child>が入る余地などありません。
 オブジェクト指向を十分理解されている方なら、これの致命的さがお分かりになるはずです。オブジェクト指向において中心となる概念がオーバーライドであり、仮想関数の存在がC++にオブジェクト指向言語としての地位を与えているにもかかわらず、親クラスのポインタに対して子クラスのアドレスを代入できないようでは、これはもはや「スマートなポインタ」どころではなく、ポインタと呼ぶことさえはばかられます。「純粋仮想関数」や「インタフェース」の存在価値も消滅します。
 これでは問題ですから、何とか実装を考えなくてはなりません。といっても、子クラスに何が来るか分からない以上、まさか様々なクラスに応じたオーバーロードを延々と書き続けることもできませんし、どうすれば良いのでしょうか。
 最初に考えたのがvoid*型を使う方法でした。しかし、確かにコンストラクタやoperator =で受け取った値を無理やりvoid*にキャストして保管し、使用時や破棄時にそれを(T*)にキャストすることはできそうではありましたが、これでは全く無関係な型を代入できてしまいます。これのどこがスマートなポインタなのでしょうか。危険極まりないことであり、当然却下です。
 他にもいくつか考えましたが、これといった妙案はありません。やはり生のポインタ型に対して直接代入するのが最も良いのでしょうが、スマートポインタのラッピングを解くわけにはいきません。かといって、Javaのように? extends Tなどといった指定ができない以上、親クラス型と子クラス型を取るテンプレートクラスはそれぞれ全く別物であり、何の関係もないクラスです。共通点があるとすれば、そのテンプレートクラスに保持されているT*型のポインタのみです。
 ここでふとひらめきました。親クラスと子クラス型を取るテンプレート同士に互換性はなくても、生のポインタ代入なら行えるはずです。代入が可能かどうかはコンパイル時にチェックするのですから、次のような記述が可能ではないでしょうか。
template<T> class Pointer{
public:
	T *ref;
	template<V> Pointer(Pointer<V> &p){
		ref = p.ref;
	}
	template<V> Pointer& operator =(Pointer<V> &p){
		ref = p.ref;
	}
}
 ここでのV型はダミーです。Javaなら?型を使えるのですが、C/C++では何らかの型を指定することが必要とされているようですので、このような形になりました。
 T型及びV型はコンパイル時に評価され、ポインタが代入互換かどうかもコンパイル時に判定されるのですから、正しいポインタの代入は問題なく行えますし、誤ったポインタの代入はコンパイル時に検出してくれます。
 これを踏まえて作成した修正版が以下のコードです。
class Counter{
	int count;
public:
	Counter();
	void operator ++(int);
	bool operator --(int);
	bool IsAlive();
};
Counter::Counter(){
	count = 1;
}
void Counter::operator ++(int i){
	count ++;
}
bool Counter::operator --(int i){
	count --;
	return IsAlive();
};
bool Counter::IsAlive(){
	return count > 0;
};

template<class T> class Reference{
	T *value;
	Counter *counter;

public:
	Reference(T *v){
		value = v;
		counter = new Counter();
	}
	template<class V> Reference(Reference<V> *p){
		value = p->GetValue();
		counter = p->GetCounter();
		(*counter) ++;
	}
	~Reference(){
		std::cout << counter << std::endl;
		if(!((*counter) --)){
			delete counter;
			delete value;
		}
	}

	// 本来は 自身と Pointer に対して friend し、private にすべき関数
	T* GetValue(){
		return value;
	}
	Counter* GetCounter(){
		return counter;
	}
};

template<class T , class R = Reference<T> > class Pointer{
	R *ref;
public:
	Pointer(){
		ref = NULL;
	}
	Pointer(T *p){
		ref = new Reference<T>(p);
	}
	template<class V> Pointer(Pointer<V> &p){
		ref = new Reference<T>(p.GetReference());
	}
	template<> Pointer(Pointer<T> &p){
		ref = new Reference<T>(p.GetReference());
	}
	~Pointer(){
		ReferenceRelease(ref);
	}
	Pointer& operator =(T *p){
		// すでに ref にデータが存在するなら削除
		ReferenceRelease(ref);

		// 新しい Reference を確保
		ref = new Reference<T>(p);

		return *this;
	}
	template<class V> Pointer& operator =(Pointer<V> &p){
		return Equal(p);
	}
	template<> Pointer& operator =(Pointer<T> &p){
		return Equal(p);
	}
	template<class V> Pointer& Equal(Pointer<V> &p){
		// 右辺値として自分自身が渡されている(p = p など)
		// かもしれないので、削除は登録後に行う
		// 削除用のポインタを取得
		Reference<T> *r = ref;

		// 受け取った Pointer の Reference を登録
		ref = new Reference<T>(p.GetReference());

		// すでに ref にデータが存在するなら削除
		ReferenceRelease(r);

		return *this;
	}
	T& operator *(){
		return *(ref->GetValue());
	}
	T* operator ->(){
		return ref->GetValue();
	}
	template<class V> bool operator ==(Pointer<V> &p){
		return ref->GetValue() == p.GetReference()->GetValue();
	}
	template<class V> bool operator !=(Pointer<V> &p){
		return ref->GetValue() != p.GetReference()->GetValue();
	}

	// 本来は自身に対して friend し、private にすべき関数
	R* GetReference(){
		return ref;
	}
private:
	void ReferenceRelease(Reference<T> *p){
		if(p != NULL){
			delete p;
		}
	}
};
 コメントでも触れていますが、実はこのクラスにも欠陥があります。例えばpublic関数のPointer::GetReferenceを使うと、本来はカプセル化の内部でのみ使われるべきクラスであるReferenceが得られてしまいます。さらに、ReferenceからGetValue()を呼び出すことで、ポインタ自体にアクセスできてしまいます。
 これを防ぐには、例えばReference::GetReferenceならprivate化すれば良さそうなものですが、それをすると「template<class V> Pointer& operator =(Pointer<V> &p)」や「template<class V> Pointer(Pointer<V> &p)」で問題が発生します。というのも、次のようなコードを書いたとすれば、
Pointer<Child> c = new Child();
Pointer<Base> b = c;
 Pointer<Base>のV型を取るコンストラクタ(template<class V> Pointer(Pointer<V> &p))内でPointer<Child>::GetReference()にアクセスすることになります。そして、コンパイラにとってはPointer<Base>とPointer<Child>は全くの別物であるため、別のクラスとみなされてprivateアクセスすることができないのです。
 無論、これを解決する方法はあります。friendによって自分自身からのアクセスを許可すれば良いのです。
template<class T> class Pointer{
	// ...
	template<class T> friend class Pointer;
	// ...
};
 ところが、あろうことかVC++ 6.0はこの構文に対応していません。したがって、危険であってもpublicアクセスできる関数によってアクセスしなければなりません。新しいコンパイラをお使いの方は、必ずfriendによる記述を使うようにしましょう。
 それからオーバーロードに関する注意点をもう1つ。上記ではコンストラクタ及びoperator =に対して次の2つのPointer&型関数を定義しています(以下の記述はoperator =の場合)。
template<> operator =(Pointer<T>);
template<V> operator =(Pointer<V>);
 なぜこのようなことが必要なのかといいますと、もし前者の記述を行わなかった場合、同じテンプレート型のPointer同士を代入すると、なぜかデフォルトのコピー代入が行われてしまうのです。したがって、しっかり2つの関数を定義する必要があるのですが、内部でやっていることは基本的に同じです。
 では実際に使ってみましょう。派生クラス型ポインタの基底クラスへの代入といえば、何といっても純粋仮想関数です。
class View{
public:
	virtual void view() = 0;
};
class CoutView : public View{
public:
	void view(){
		std::cout << "cout" << std::endl;
	}
};
class PrintfView : public View{
public:
	void view(){
		printf("printf\n");
	}
};
class PutsView : public View{
public:
	void view(){
		puts("puts\n");
	}
};
class DummyView : public View{
public:
	void view(){
	}
};

Pointer<View> GetView(char *type){
	Pointer<View> p = new DummyView();
	if(strcmp(type , "cout") == 0)
		p = new CoutView();
	else if(strcmp(type , "printf") == 0)
		p = new PrintfView();
	else if(strcmp(type , "puts") == 0)
		p = new PutsView();

	return p;
}

void main(){
	Pointer<View> v[4];
	v[0] = GetView("cout");
	v[1] = GetView("printf");
	v[2] = GetView("puts");
	v[3] = GetView("undef");

	for(int i = 0; i < 4; i++)
		v[i]->view();
}
 何ともありがたいことに、見事に動作してくれます。メモリ確保や解放を考える必要もありません。さすがにスマートポインタと呼ばれる技術だけはあります。
カテゴリ [開発魔法][社会問題] [トラックバック 0][コメント 0]
<- 前の記事を参照 次の記事を参照 ->

- Blog by yamicha.com -