MASATOの開発日記 2000年10月


10/29

真偽値にはBOOLとboolのどちらを使うべきなんだろうか?この議論の結果はわかっている。 それは「好み」だ。でも本当に「好み」なのか?と考えてみた。BOOLは、実際にはint。 boolは、C++で導入された新しい型。WIN32APIは、BOOLしか使わないので、私は今までBOOLを使用してきた。 しかし、boolを使ってみなければ分からないという事で、boolを使ったコードを書いてみてみた。 結果・・・使い心地が変わらない!今までBOOLと書いてきた所をboolと書いてTRUEをtrueに、FALSEをfalseに直しているだけだ。 BOOLとboolが混在しても問題も発生しない。真偽判定は、if(b)や、if(!b)として判定しているので、 特に気にすることも無い。コードを書く手間はBOOLとboolのどちらを早く打つ事が出来るか、に左右されると言えよう。 Shift押さなくて良いからboolかなぁ・・・って大差ないので比べるのは止めておく。
そうすると、残るは実行時の消費メモリや速度である。といっても詳しく検証したわけじゃないので適当な想像である。 消費メモリ・・・VC++6.0ではBOOLは4バイト、boolは1バイトらしいので、boolの方が小さそうだ。 しかし、この程度の差が問題になるのは、巨大なBOOL配列を作った時くらいだ。 果たして巨大なBOOL配列を取る事があるのか?まず無い。もしあるときはBOOLじゃなくてBYTEにすることをお勧めする。 実行速度・・・コンパイラ次第なんですよね。でも普通4バイトの方が速そうです。 結局どうなんだろうか。もしC言語で使われることを予想しているならためらうことなくBOOLだ。 であるのだが、私が作るコードは、class使いまくりで、C言語で使われることをまったく考えていないので、 boolでもなんら問題無い。というわけで結論は「好み」である。ヽ(´ー`)ノ

(Visual C++)CSocketの使い方(その3)。

前回概要だけ作った関数のコードを書いていきます。
まずはサーバーアプリケーションから。

CAcceptSocketに次のメンバ関数を追加します。

void CAcceptSocket::OnAccept(int nErrorCode)
{
	CClientSocket* client = new CClientSocket();
	Accept(*client);
	client->Init();
}
ちなみにこのコードは、clientをdeleteする事を考えていない。 もちろんこれではまずいので、clientをどこぞにリストや配列に保管しておき、しっかりとdeleteしよう。

CClientSocketに、次のメンバ変数を追加。

CSocketFile* m_lpFile;
CArchive* m_lpInput;
CArchive* m_lpOutput;
実際のデーターはこれを使って送受信する。

続いてCClientSocketにメンバ関数を追加する。

CClientSocket::CClientSocket()
{
	m_lpFile = NULL;
	m_lpInput = NULL;
	m_lpOutput = NULL;
}
CClientSocket::~CClientSocket()
{
	Release();
}
void CClientSocket::Init()
{
	Release();
	m_lpFile = new CSocketFile(this, TRUE);
	m_lpInput = new CArchive(m_lpFile, CArchive::load);
	m_lpOutput = new CArchive(m_lpFile, CArchive::store);
}
void CClientSocket::Release()
{
	if (m_lpInput != NULL)delete m_lpInput;
	if (m_lpOutput != NULL)delete m_lpOutput;
	if (m_lpFile != NULL)delete m_lpFile;
	m_lpInput = NULL;
	m_lpOutput = NULL;
	m_lpFile = NULL;
}
あとは、CClientSocketの、m_lpInput,m_lpOutputを用いて送受信を行いましょう。 普通のCArchiveと使い方は同じです。
m_lpOutputにデーターを送った後、すぐ相手に届くようにしたいならば、 m_lpOutput->Flush()を呼び出しましょう。 これを送らないと何時までたっても相手に届かないときがあります。

クライアントアプリケーションについてはまた次回。

(2000/1/14) 色々推敲。

10/10

 10/7日の続きで行きます。あるクラスAに、初期化関数Initがあったとします。これを、ただのpublic関数とし、Init内部でクラスAの初期化をし、InnerInitというprotectedかつvirtualな関数を呼び出します。クラスAを派生させたクラスでは、InnerInitで初期化を行います。これで、外部からは「基底クラスだけを初期化」という事はできませんので比較的安全になります。そして、この書き方にはメリットもあります。Aの初期化のためだけに必要な変数は、InnerInitには必要ないので、InnerInitは引数の数を少なくできます。これは結構便利でした。しばらくはこの方針でコードを書いてみて問題点を調べてみようかな〜と思ってます。
 こうして考えて行くと、理想的な初期化関数はコンストラクタなのです。確実に、基底クラスから順番に派生先の全てのクラスが呼び出されるのですから。ですが、コンストラクタだけでは実現できない事もあるので、別に初期化関数が必要になってきてしまうのです・・・。うーむ。
 

(Visual C++)CSocketの使い方(その2)。

 前回の概要に引き続き、今回はクラスを書きます。MFCのクラスウィザードがサポートしてくれるので結構楽だと思います。
 まず、サーバーアプリケーションには、Accept用とSend&Recv用の2つのCSocketが必要となります。Accept用のCSocketでクライアントの接続を感知し、そこで、クライアントとのデーターをやりとりするSend&Recv用のCSocketを作成するわけです。実際の手順は以下のようになります。
  1. クラスウィザードを用いて、CSocketを派生させて、CAcceptSocketというクラスを作成する。(名称は適当です)

  2.  
  3. CAcceptSocketの、OnAccept関数をオーバーライドする。

  4.  
  5. クラスウィザードを用いて、CSocketを派生させて、CClientSocketというクラスを作成する。

  6.  
  7. CClientSocketの、OnReceive関数及びOnClose関数をオーバーライドする。
  8. CClientSocketに、

  9. -----
    void Init();
    ----
    という初期化関数を追加する。(本当は名前は何でも良い)
これでとりあえずサーバー側は終わりにして、次はクライアントアプリケーションに、サーバーとの通信用クラスを作成します。
  1. クラスウィザードを用いて、CSocketを派生させて、CConnectSocketというクラスを作成する。

  2.  
  3. CConnectSocketのOnReceive関数及びOnClose関数をオーバーライドする。
  4. CConnectSocketに、

  5. -----
    void Init();
    -----
    という初期化関数を追加する。(上と同様に、実際の名前は何でも良い。)
とりあえずこれで雛型は出来ました。あとは中身をつめていくのですが・・・
それはまた次回(^^;
(11/3)Init関数を追加。

10/7

 最近オブジェクト指向の手法について悩む日々が続いております。C++はオブジェクト指向というよりもクラス指向といった方があっているんじゃないかと思うんですが、オブジェクト指向という単語が広まっているのでこれからもこれを使います。
 あるクラスのpublicな関数は、どのような順番で呼ばれても正しく動作する(不正なメモリアクセス等をしない)ようにするべきです。ファイル操作のような、Open→Read→Closeの順番で呼ばれるようなクラスは、Open前にReadをすると、エラーを返すべきです。Closeをする前にOpenをした場合は、エラーを返すか、Open内部でCloseを呼ぶべきでしょう。ですが、このクラスが、基底クラス(これにはvirtual関数Open,Read,Closeがある)を派生させたものであった場合、クラスオブジェクト.基底クラス名::Openという書き方で、基底関数のpublic関数が直接呼べちゃうんですよね・・・。これにはまいります。派生させた方のクラスでReadで読みこんでいる時に、いきなり基底クラスのOpenを呼ぶと、メンバ変数の一部分だけ(基底クラスに属する部分)が初期化されます。そこ結果、大概はオブジェクトが壊れます。色々と考えてみましたが、OpenやInitのような、基底クラスの初期化を行うような関数をvirtualにしてしまうと、直接これを呼び出されてしまった場合、派生クラスのオブジェクトが壊れる可能性大です。
 いや、そんなへんな呼び方したら壊れるのはあたりまえだ、と思われるかもしれません。ですが、上でも述べているとおり、「publicな関数は、どのような順番で呼ばれても正しく動作する」というのが理想なので、オブジェクトが壊れるのはなんとか避けたい所です。
 解決策は、Openを非virtualなpublic関数とし、InnerOpenというvirtualでprotectedな関数を追加して、Openの内部でInnerOpenを呼ぶのです。これだと、virtualな関数はprotectedなので外部からは呼べませんので、かならず派生クラスのInnerOpenが呼ばれ、ここでエラー処理をすればOKです。多少面倒になるんですよね。派生クラスでの自由度も下がりますし。う〜ん。どっちが良いのだろうか・・・と悩む日々。

(Visual C++)CSocketの使い方(その1)。

 結構長くなるので、数回に分けて説明します。今回は概要だけ・・・。
 本来のsocket系の関数は、recv関数のような、データーを受信するまでずっと待つ、といったような、ひたすら待つだけの関数が多いです。データーが送られてこないときにrecvを実行すると送られてくるまでずっと待ってます。しかも、今データーが送られてきているかどうかの判別が出来ないのです。というわけで、うかつにrecv関数を呼んでしまうと、延々データ−受信待機状態、つまり見た目固まった状態となるわけです。ウィンドウメッセージの処理も出来ないので、ウィンドウの再描画も行われませんし、終了ボタンも反応しません。ではどうすれば良いかというと、スレッドを新しく作って、そちらで受信処理をするわけです。10台のマシンと繋がっていたら、スレッドを10個作ってそれぞれでrecvをするわけです。別にこれはこれでも構わないのですが、それよりも、データーが送られてきたら、そこで始めてrecvを呼ぶ、とすればスレッドを作る必要もなくて、楽ですね。データーが送られてきたら、ウィンドウメッセージが送られてくるようになれば、受信待機状態がメッセージループと両立できて、とても便利です。これを実現したのが拡張socket。それを簡単に使えるようにしたのがMFCのCSocketクラスというわけです。
 CSocketは、内部に隠れたウィンドウを作ってそこにメッセージを送ってもらっているらしく(推測)、メッセージループ等複雑な事を考えなくても扱えます。
 ・・・と今日はとりあえずここまで。



戻る
トップページへ
masato@mb.kcom.ne.jp