プログラミング所感 - 前へ 目次 次へ


99/1/18(月)

とうとう 1999 年である。 また、1ヶ月ぶりの更新。この前買った、「プログラミング言語C++第3版」は 結構、面白い。いろいろな話題がある。
例えば、
    void fc(char&){}
    void fi(int&){}
    int main() {
        signed char   sc;
        unsigned char uc;
        signed int    si;
        unsigned int  ui;
        fc(sc);            // (1)
        fc(uc);            // (2)
        fi(si);            // (3)
        fi(ui);            // (4)
    }
この中で、どれがエラーになるかわかるだろうか?

答えは、(3)以外全部である。

char が signed か unsigned かは処理系定義である。int は、signed である。 従って、正しい呼出しは、(3) だけである。 なになに、「(1) か (2) のどちらかは正しい筈だ」って? 確かに、VC++ では char は signed である。でも、VC++ で fc(sc); はエラーだ。 他の処理系に持っていって始めてエラーになるよりは、予めエラーになっている方が いいに決まっている。

結局、char は 3種類あるが、int は 2種類しかないのだ。 つまり、「signed int」と書く必要は全くない。

ちなみに、上のプログラムで main に return を書かなかったのは わざとである。理由は、件の本の§3.2 を見て欲しい。


99/1/20(水)

私的派生と限定派生について思ったこと。
私的派生と限定派生は、実装の継承として用いられるようである。 これは、私的メンバや限定メンバを持つようにしても実現できる。 私的(限定)派生するのとメンバに持つことの違いは以下の点を除いて殆どない。
  • メンバの場合、変数名で参照する。派生の場合は、クラス名か this で参照する。
  • ユーザからは派生関係にないが、メンバ関数内では派生関係にある。従って、
        Derived    d;
        Base*      p = &d;
    
    は、メンバ関数内で有効である。(但し、Base は実装を規定したクラス。 Derived は、Baseから私的 or 限定派生したクラス。)
  • 私的(限定)派生であっても、上のように部分的とはいえ、派生関係である。 従って、ポリモーフィックに影響を与える。
最後の点は、次のような例で違いを確認できる。
ケース1
#include <iostream>
using namespace std;

struct Base {
    virtual ~Base(){}
};
struct D1 : private Base {
    virtual ~D1(){}
};
struct D2 {
    virtual ~D2(){}
private:
    Base b;
};

int main()
{
    cout << sizeof(D1) << endl;
    cout << sizeof(D2) << endl;
}

この例では、D2 の方が D1 よりオブジェクトのサイズが、 仮想関数テーブルのポインタ(以下、vfptr)1つ分大きくなる。 これは、D1 が vfptr を1つしか持たないのに、D2 は2つ持つからである。
ケース2
#include <iostream>
using namespace std;

struct Base  {        ~Base (){} };
struct BaseV {virtual ~BaseV(){} };

#define TEST_DEF void test(){cout << typeid(*this).name() << endl;}
struct Derived  : private Base  {TEST_DEF};
struct DerivedV : private BaseV {TEST_DEF};

struct MoreDerived  : public Derived  {};
struct MoreDerivedV : public DerivedV {};

int main()
{
    MoreDerived     d;
    MoreDerivedV    dv;
    Derived*        p  = &d;
    DerivedV*       pv = &dv;
    p ->test();
    pv->test();
}

この例では、pv->test() は、正しく機能するが、p->test() は機能しない。 何故なら、typeid は仮想関数を持つクラスでないと利用できないからである。

結局、私的(限定)派生であれ、派生に伴う制約を受ける。 例えば、複数の機能を利用する場合、派生で実現しようとすると多重継承になり兼ねない。 従って、派生よりはメンバで実現した方がすっきりするといえる。 私的 or 限定派生を使う必要はないと思う。

参考00/10/23の話


99/3/3(水)

オーバロードとスコープの関係について誤解していた。
オーバロードは同一スコープだけで解決される
fj.comp.lang.c++ の記事を読んで気づいたが、ARM 13.1にも出ていた。

例としては、
struct B {
    void f(int){}
};
struct D : B {
    void f(char*){}
};
int main()
{
    D    d;
    d.f("A");   // OK
    d.f(1);     // NG
}

今まで、f(1) は OK と思っていた。

結論 − オーバロードされている関数群の1つでも派生クラスでオーバライドしたら 全てオーバライドし直さなければいけない。(using B::f; などとしてもよい。)


99/5/24(月)

最近、忙しくて3ヶ月ぶりくらいの更新だが、 内容はたいした話しではない。 この前、デバッグしていた時のことであるが、結果がおかしいので、 常に整合性を見るようにし、おかしくなった時点で、すぐ判るようにした。 で、判ったら、その条件になったら止まるようにして、もう一度動かすのだが、 そうすると、そこではおかしくならずに違う所でおかしくなるのだ。 その条件で止まるようにしても、また別の所に移ってしまう。
不確定性定理みたいに見ようとしても見えない。 デバッガのブレークポイントを変えただけで、結果が変わってしまうのだから わけがわからない。結局、バグは取れたのだが、原因は良く分からないままである。

99/6/16(水)

条件チェックのスピードを比較してみた。

x が x1とx2の間に入るかどうかを調べたいとする。 但し、x1 < x2 かどうかはわからない。方法としては、次のように2つ考えられる。
    (x-x1) * (x-x2) <= 0                          // (1)
    (x1 <= x && x <= x2) || (x1 >= x && x >= x2)  // (2)

私は、(1)の方が分かり易くて好きだが、VC++でスピードを測ると (2)の方が、30%ぐらい速い。 しょうがない、(2)で書くことにしよう。 細かいことを言えば、(2)では、最大4回比較を行う可能性がある。(x1 == x, x2 < x)

次のように書けば、最大は3回で済む。
    !((x < x1 && x < x2) || (x > x1 && x > x2))

最初の ! をとれば、次のように書ける。
    (x >= x1 || x >= x2) && (x <= x1 || x <= x2)

これより次の様に書く方が比較回数が減る可能性がある。
    x < x1 ? x >= x2 : (x <= x2 || x == x1)

全部計測してみたが、(1)以外はどれもスピードは変わらなかった。


99/6/17(木)

この前、歯医者の予約をすっぽかしてしまった。
で、スケジュールを忘れないように、指定時刻にメモが出るような ちょっとしたアプリを作った。Windows98 や NTなら、タスクマネージャでできるし、 UNIX なら at でできる。そろそろWindows98に変えてみようかな。

99/6/22(火)

ちょっと思ったこと。 オブジェクト指向風にプログラム書いていくと、
「これは、アンタに任すわ」
っていうような、自分で何もせずに仕事を振り分けてるだけのクラスとか よく作っちゃうような気がする。で、いろいろなクラスの間を取持つ役目に なるんだけど、何かいい身分だよな。

話は変わるけど、自分のプログラミングの癖を見つけた。

「プログラムを書いてから考える」

99/6/25(金)

MS 特有の機能に失敗した。 min,max を使っていたら、どうもおかしい。 ヘルプを見ても、テンプレートなのに副作用がある。で、やはりマクロだった。
VC++ では、min,maxが3種類もあった。
  • min,max
  • __min,__max
  • _MIN,_MAX
驚くことに、上2つがマクロで、一番下だけテンプレートであった。 そりゃないんじゃないの。

99/7/19(月)

配置車両のデータ構造をどうすればよいか悩んだ。
最初は、単純に1個所で保持していた。(1)
配置データは主に、
  • 車両の表示
  • 障害物として認識
などで使用される。このように、処理はデッキ単位であるため、 次に、デッキ毎に持たせるようにした。(2)
ところが、このままだとスロープ上の車両が問題になる。 スロープ上の車両は複数デッキで見える可能性があるからだ。
かといって、スロープ上の車両は見えるデッキに冗長に持たせるのも 整合性がくずれやすい。そこで、スロープ上の車両は、デッキに持たせるのではなく 船に持たせるようにした。(3)
このとき、今まで別のクラスで管理していた「コピーやUNDOバッファ」用の データを都合上、配置データに合併させた。(構成が同じなので。 別のままだと、構成が一致するように気をつけていけない。)
しかし、このことにより同じ配置データをデッキと船の2個所で管理するのが 不便になった。そこで、配置データをまとめて管理するクラスを作成した。(4)
これでも、スロープ上のデータは、デッキ毎に参照されるので、 1つのデータをデッキの数だけチェックされる。
結局、デッキで表示されるべき車両は、デッキ上かスロープ上かに関わらず、 同じようにアクセスできるようにした。(vector<CPlace*>として) このため、配置データをデッキ及びスロープで持つことにした。(5)
なぜ、始めからそうしなかったかは聞かないで欲しい。

99/7/21(水)

今日、見つけた VC++6.0 のバグ
#include <vector>
int main()
{
	std::vector<int>>	i;
}

コンパイルエラーにならない!g++ はなるぞ。


99/7/23(金)

今日、見つけた VC++6.0 のバグ第2弾
void f(int*){}
int main()
{
    f(false);
}

コンパイルエラーにならない!g++ はなるぞ。


99/9/8(火)

参照メンバ変数について。
メンバに参照変数があると、デフォルトでは代入演算子が作られない。 これは、正しい仕様に思える。 実際のところ、「参照メンバ変数は、可能だが使うことはない」と思っていた。 ところが最近、人の書いたソースで次のようなのを見た。
struct CDblPos : public pair<double,double>
{
  CDblPos(const double& a,const double& b)
    : pair<double, double>(a,b), x(first), y(second) {}
  CDblPos(const CDblPos& a)
    : pair<double, double>(a), x(first), y(second) {}
  CDblPos& operator=(const CDblPos& a)
    {x = a.x, y = a.y; return *this;}
  double&  x;
  double&  y;
};

最初、何だこりゃと思ったものの、よくよく見ると正しいし、理にかなっている。 恐らくこれが唯一の参照メンバ変数の正しい使用法だろう。
すなわち、「参照メンバ変数は、自分のメンバ変数のみ参照できる」こと。 この場合、参照メンバ変数は、単に呼び方を変えているにすぎないが、参照とはそういうものだ。
結局、次のように書ければ、C++ の仕様として望ましいと思う。
struct CDblPos : public pair<double,double>
{
  CDblPos(double a,double b){x = a; y = b;}
  double&  x = first;
  double&  y = second;
};

こうすれば、代入演算子も定義しなくて良いから、よけいなものを書かなくて済む。 まぁ、今のままの仕様の方がすっきりするけど。
5/15の話参照。


99/9/9(水)

代入とswap の速度は、どちらが速いか?
次のコードで考えてみて欲しい。
  list<XXX>	i,j;
  i = j; // (1);
  i.clear(); i.swap(j); // (2);

もし、代入後の j の値を使わなければ、(1) でも (2) でもよいはずだ。 違いは、代入ではコピーコンストラクタが呼ばれるのに対し、 swap では、呼ばれないという所だ。だから、swap の方が速いと思われる。 実際に計ってみると、XXX が int では、代入の方が速いようである。 何故だかよくわからない。

今日は、9並びの日だ。そう言えば、一昨年は年号が9だった。


99/9/20(月)

カーソルの表示について。 OnSetCursor で、モードに応じてカーソルの 表示を変えていたのだが、最初の1回は変わるのだが 別のモードにしても変わらないことがあった。 特徴的なのは、起動直後モードAにすれば、 カーソルもAになるのに、モードBにしても 変わらない。また、起動直後モードBにすれば、 カーソルもBになるのに、モードAにしても 変わらないことだ。結局、半日かかって原因は分かった。 LoadCursor でカーソルを作るとき、絵のビット情報から あるハッシュ値を作っているようだ。それで、そのハッシュ値で カーソルの同一性を判断しているらしい。従って、絵が似ていると 同じカーソルと判断されてしまう。結局、回避するために 絵をずらしたりして変更せざるを得なかった。

99/11/1(月)

MFC の GDI オブジェクトについて。 リソースがリークしていて原因を調べたら、 CDC::SelectObject で選択していた GDI オブジェクトを DeleteObject していたせいだった。

99/11/22(月)

Java の話である。 C++ の所感であるが Java の話をちょっと。 Java に関する備忘録的なページをJAVA One Pointにまとめた。 この中で、電卓のサンプルが作ったのだが、 疑問がいろいろでてきた。
  • button.setBounds(...); を、Applet クラスで実行するときと、 別の関数で実行するときで結果が異なる。C++ のプログラムでは考えられない。
  • addActionListener(new ActionListener(){public void actionPerformed(ActionEvent e){actionP();}});

    みたいなことが書けてしまう。分かりにくいと思うが、関数呼出しでクラスを定義してしまっているのである。
    さらに、納得いかないことに、actionP() は、CalcApplet のメソッドなのにエラーにならない。 (C++ ならば、actionPerformed が、CalcApplet のメンバでないとエラーになる。)

謎だ。00/3/14の話も参照。

99/11/24(水)

C Magazine の記事を読んでたら、 前に一度書いたこと と同じようなクラスを作っている記事があった。しかし、参照カウント付き配列用クラスも、別クラスとして作っていた。記事の作者は、STL の本書いているくせに、センス悪いね。

前へ 目次 次へ