2018-01-19

Cプログラマのための、
C++オブジェクト指向簡単裏口入門(7)

オブジェクトはどのように対象を写像するのか?(1)

名刺管理のアプリケーションの設計で考えていきましょう。
要点以外の詳細はできるだけ省きます。

オブジェクト指向の設計では、何がオブジェクトか、その全てを洗い出すところから始める、というのが常套手段です。でも、今回は設計手順を学ぶ記事ではないので、自由に考えていきます。

名刺管理ですから、当然名刺がオブジェクトになると考えてみます。では、名刺クラスを作りましょう。

コード1

// 名刺クラス定義
class NameCard
{
    string  m_name;         // 氏名
    string  m_mobTel;       // 携帯番号
    string  m_email;        // メアド
    string  m_title;        // 肩書き
    string  m_company;      // 所属会社
    string  m_department;   // 部署
    string  m_address;      // 会社住所
    string  m_tel;          // 電話番号
    string  m_date;         // 名刺交換日
    string  m_memo;         // メモ
};
とりあえず、名刺に記載されている情報と、名刺交換日とメモを加えてみました。
処理についてはまだ当分は後回しにします。

ところで、この名刺管理アプリにはどのような機能があるのでしょうか。(もちろん、本来であれば機能要件は先に決まっているはずです)

  • 検索機能(入力項目を条件にした絞り込み検索)
  • 宛名印刷(宛名ラベルやはがきの宛名印刷)

今回はこの2つの機能があると仮定します。

このうち宛名印刷は、個別の名刺宛に書類の送付等用以外に、年賀状等の一斉送付用も含めます。

さて、ここで機能上の問題が考えられます。

このクラスだと名刺1枚ずつをそのまま入力することになります。しかし、同じ会社に所属している人の名刺もたくさんあります。同じ会社なのに、毎回入力することになります。
とすると、年賀状を各所属会社に一通ずつ送付したいと思っても、所属が一括管理されていないので難しいというのはご理解いただけるでしょうか。
入力が正確で、一言一句、スペースの入力や全角半角の文字まで全て一致していて、なおかつ入力ミスが無ければ印刷するときに動的に名寄せする(重複を1つにまとめる)ことも可能ですが、人が入力する以上正確性は当てにできません。

この問題には印刷時だけでなく、所属会社の事務所が移転する等修正が必要になった場合等にもぶつかります。

そこで、所属する会社を分けて管理することが思いつきます。

コード2

// 名刺クラス
class NameCard
{
    string  m_name;         // 氏名
    string  m_mobTel;       // 携帯番号
    string  m_email;        // メアド
    string  m_title;        // 肩書き
    string  m_date;         // 名刺交換日
    string  m_memo;         // メモ
    Company * m_pCompany;   // 所属会社(NULLで所属無し)
};

// 会社クラス
class Company
{
    string  m_name;         // 会社名
    string  m_department;   // 部署
    string  m_address;      // 住所
    string  m_tel;          // 電話番号
};
会社クラスが新たにできました。そして、名刺クラスには会社クラスのオブジェクトを1つ所有できるようにしています。
これで概ね良さそうですか?

でも、もしこの名刺管理アプリを僕が使うのだとしたら困ることがあります。
同一人物が複数の会社やサークルなどに属していて、その人から所属の異なる名刺を複数枚もらうことがあるからです。
会社は名寄せした状態で管理できるようになりましたが、今度は人物です。

次のように解決しましょう。
名刺クラスと会社クラスの関係が、名刺クラス側から見た場合、今は1:0or1ですが、これを1:0~nとすることです。

ここで、ついでにもう1つ解決しておきたいことがあります。所属無しの名刺の場合(自由業など、個人名刺の場合)、連絡先住所などの入力がなくなってしまうので、名刺クラスの方にも住所などが必要になるということです。
この項目は個人の自宅住所になりますが、通常名刺には書かれていませんね。しかし、親交が深まる等で個人宅当てにお歳暮を贈る、ということは良くあることなので、会社などに所属している場合でも利用できます。。

では、コードを修正してみましょう。

コード3

// 名刺クラス
class NameCard
{
    string  m_name;                // 氏名
    string  m_mobTel;              // 携帯番号
    string  m_email;               // メアド
    string  m_address;             // ★1 住所(通常は自宅)
    string  m_title;               // 肩書き
    string  m_date;                // 名刺交換日
    string  m_memo;                // メモ
    vector<Company *> m_pCompanys; // ★2 所属会社 0~n
};

// 会社クラス
class Company
{
    string  m_name;         // 会社名
    string  m_department;   // 部署
    string  m_address;      // 住所
    string  m_tel;          // 電話番号
};
★1の部分、名刺クラスに住所項目を追加しました。
★2は、vector という動的配列で所属会社を複数登録できるようにしました。もちろん、要素数0も可能です。

ところで、ここまであえて書かなかったのですが、クラスをどのように実体化してプログラムの中に持つのでしょうか。疑問に思われた方もいるかも知れません。

個人も所属先の会社も名寄せした状態(重複させない)のですから、それぞれが配列のように実体を保持する状態だ、というところまではよろしいでしょうか。今はデータベースのことは考えず、メモリ上に全てのデータがあると考えてください。

単純に、それぞれは vector 配列に保持されているとすると、

コード4

vector<NameCard>    nameCards;
vector<Company>     companys;
どこかにこんな感じで保持されていることになります。

NameCardクラスには Company クラスのポインタを要素とする配列の項目がありますね(コード3)。ここに、コード4の companys の要素のうち、所属するもののポインタを保持することになるわけです。

これで、とりあえず多対多が成立はするのですが、会社クラスには関連付けの項目が無いので、ある会社に着目して、所属社員全てを見たいときは、このままだと nameCards を全て洗い出さなければなりません。これでは非常に処理に時間を要してしまいますので、やはり、会社クラスの方にも関連付けのための項目を持たせておいた方が都合がいいですね。

コード5

// 名刺クラス
class NameCard
{
    string  m_name;                // 氏名
    string  m_mobTel;              // 携帯番号
    string  m_email;               // メアド
    string  m_address;             // 住所(通常は自宅)
    string  m_title;               // 肩書き
    string  m_date;                // 名刺交換日
    string  m_memo;                // メモ
    vector<Company *> m_pCompanys; // 所属会社 0~n
};

// 会社クラス
class Company
{
    string  m_name;                   // 会社名
    string  m_department;             // 部署
    string  m_address;                // 住所
    string  m_tel;                    // 電話番号
    vector<NameCard *> m_pNameCards;  // ★社員 0~n
};
会社クラスに社員項目を追加しました。

これで、NameCard 側からも Company 側からも所属が瞬時に判る訳です。もちろん、しっかりと整合性を持たせて、どちらかの片思いの状態にしてはいけません。

ただし、データの保持にデータベースエンジンを使用する場合、これらのクラスは一時的な使用に留まるため、関連付けの項目自体不要になる可能性もあります。関連付けはDB上で行われていて、関連のあるデータのみを読み出す場合が多いかもしれないからです。つまり、両方とも関連のあるデータしかメモリ上に存在しない状態であれば、関連付け情報自体必要が無いというわけです。
あるいは、もし関連情報が必要になった場合でも、その時点でデータベースに検索させる、ということも十分考えられます。

ところで、そろそろ説明に窮屈さを覚えてきましたので、1つ重要な用語を覚えてください。
今まで、漠然とオブジェクト、という言葉を使ってきましたが、「インスタンス」という言葉を登場させます。

インスタンスとは、クラスを実体化したオブジェクトのことです。クラスがハンコだとすると、それで押した印影がインスタンスです。クラスからインスタンスを作ることをインスタンス化すると言います。つまり、インスタンスは狭義の意味でのオブジェクトと言えます。
お堅いオブジェクト指向用語の1つですが、現場でもよく登場する言葉なので是非、覚えておいてください。

先ほどのデータベースの話に戻りますと、データベースから特定のデータを読み出してきたものをクラスのインスタンスに保持する、という言い方になります。


今回はここまでにします。
しかし、実はまだまだこれらのクラスには合理化できる余地がたくさん残されています。次の記事以後説明していきます。

2018-01-16

Cプログラマのための、
C++オブジェクト指向簡単裏口入門(6)

変化に寄り添えるオブジェクト指向

大前提として、システム開発というものは一度の開発サイクルで終わることは希である、という点は確認しておきましょう。
バグのないシステムを構築したとしても、ユーザが使っているうちに問題点や改善点が見えてきます。そして機能の修正や追加が繰り返されて完成度が増していきます。これは悪いことではないし、成功しているシステムはそういうものです。

ソフトウエアはハードウェアに比べて変更しやすいというのは、これはもう抗えない宿命です。つまり、システム開発は潜在的に変化に追従していく義務があるのだと思うのです。

そこで、ソフトウエアの品質を計る要素の1つ、「保守性」が登場するわけです。

ここで言う保守とは、機能を維持する、という消極的なものだけではなく、システムの改築までを視野に入れたより広い範囲を指します。

保守性に問題があると

初版のプログラムは概ね設計通りにできたとしても、何度も改造を繰り返すうちにプログラムコードは時間つぶしには最適の迷路パズルゲームと化し、その難易度は高くなる一方で、最後には手が付けられなくなって開発サイクルはすぐに終焉を迎えてしまいます。

ありがちですが、どうしてそうなってしまうのでしょうか。

1つには予算の問題が挙げられます。
システムの改修にかかる費用が割高に思えるのは、新車の購入金額の割に修理の部品代が妙に高く感じられるのと似ています。車1台分の部品をバラバラに購入したら新車が何台買えることか・・・?
車の部品には定価があるので、工賃を少し負けてもらうくらいしかできません。
しかし、ソフトウエアの改修は基本は全てが人的工数ですから、どうしても無理をさせられてしまいがちです。
無理をした結果、改修の繰り返しは悪循環と化し、地獄へ落ちていくのですね。

しかし、実際にプログラムの改修というのは、100%自由に作ることができる新規開発に比べて工数がかかるのは仕方がない面もあります。

なので、これをいかに軽減できるか、そこが焦点になります。

オブジェクト指向でどのようにこの問題を解決するのか

理想はプログラム改修の工数が、その機能改修によって生み出される価値と一致するか、更にそれを下回るようにすることです。

しかし、オブジェクト指向は決して魔法ではありません。これを取り入れたからといって、一気に理想に近づける訳ではありません。しかし、そのポテンシャルは十分に備えていますから、習得の為の労は必ず報われるはずです。少なくとも、僕にはそう感じられるので、これまで楽しくオブジェクト指向プログラミングに取り組んで来ることができました。

蛇足ながら、「楽をするための労は惜しまない。」という僕のスタンスにぴったりの技術でもあります(BLOG記事『忘れられた合理性の追求』を参照のこと)。

こんなことを書くと、ここ以降は茨の道になるように誤解されるかも知れませんが、そんなことはないのでご安心ください。

オブジェクト指向の神髄を紐解いていきましょう。

肝となる写像という考え方

オブジェクト指向の聖書(偉い人が書いた書籍)には、よく現実をモデル化したものをそのままクラス(オブジェクト)にする、といったような言が書かれています。
我々プログラマはその持って回った言い方によく惑わされてしまいます。
技術者ではないマネージャがこの話を聞くと、オブジェクト指向がまるで魔法のツールのような誤解をして我々技術者を困らせることもあります(実話)。

現実にプログラムを対応させる、と言っても、コンピュータの中にはそれとは無関係なオブジェクトと呼べるものがたくさんあります。ファイルシステムやディレクトリ、ファイル、データベース、メモリやネットワーク等々。プログラムが意識する対象の多くはそんなものが中心だったりしますね。

現実をモデル化したもの、それはたとえば「人」。人というオブジェクトが、コンピュータ内部に特有のオブジェクトと同列に存在することに、違和感を覚えずにはいられませんね。

でも実は、そこは階層を分けて考えればいいだけなのです。簡単な話でした。
簡単だけど、この説明を直接書いてある書籍に出会ったことはありません。


ただし、この聖書に書かれている言葉にはとても大切なことが隠されています。
それは、オブジェクトが対象を良く表したものであることが求められるという点です。対象が現実社会の何かをモデル化したものであっても、あるいはコンピュータの内部のことであったとしても同じです。
オブジェクトはプログラムが扱う「もの」であって、対象そのものではありません。対象に対して、オブジェクトは写像であると考えます。

写像であれば、その元になる対象に変化があっても、写像も同じように変化させればよく、回りくどい修正は必要無いはずである、と考えるのですね。

従来型のプログラムの問題点

逆に、オブジェクト指向ではない従来のプログラムではどうだったかを思い出してみましょう。

まとまりのあるオブジェクトという構造は存在せず、対象を操作する関数だけがありました。ひょっとしたら関数の操作対象は1つだけではなく、同時にいくつかの対象を操作するものかも知れません。その方が都合が良かったのでしょうね。

そこで、1つの対象に変化があり、プログラムがその変化に対応しなければならなくなったとします。

まず、その対象の変化により、プログラムのどの部分に影響があるか、その全てを調べるのに大きな労力が必要です。あちこちにそれを操作する関数が分散しているからです。
そして、操作対象が複数になっている関数やプログラムの場合、無関係な対象に影響が出ないように慎重に行う必要があり、これまた膨大な時間がかかってしまうことになります。

オブジェクト指向という筋の通った考え方に沿ったプログラミングから見ると、従来型のプログラムには節操がないように見えてしまいます。


今回は具体的なプログラミングの例を示すことができませんでしたが、次回からは具体的なオブジェクトのプログラミングに入っていきたいと思います。

コラム:オブジェクト指向の習得

2018-01-14

Cプログラマのための、
C++オブジェクト指向簡単裏口入門(5)

プライバシー問題!?

とてもソフトウエア工学とは直接関係なさそうなタイトルですね。でも、実際にはプログラミングにとって非常に重要な考え方で、是非、これは覚えるだけではなく、身につけて今後に活かしていただきたいと願います。
特に、C言語だけを何の問題も感じずに長年やってきた人にとっては、ひょっとすると面倒臭い余計なものに感じられるかも知れませんので、注意が必要です。

実社会のプライバシー問題

実社会でも、以前の日本ではプライバシー問題という言葉すらほとんど聞くことはありませんでした。
ところが、時代が新しくなるにつれ、人間関係が疎になっていったし、習慣の違ういろんな人が集まるようになってくると、赤の他人に必要以上の個人情報を知られて悪用されることに、社会全体が敏感になっていったのです。

プログラミングのプライバシー問題?

プログラミングも昔はプライバシー問題に鈍感でした。パーソナルコンピュータが一般に普及し始めた頃、おまけで付いていたインタプリタ言語(N88BASIC)にはグローバル変数しかありませんでした。

しかし、多機能、高機能が求められ、プログラムが複雑、かつ大きくなってくると限界が見え始めます。そんなときに構造化プログラミングの必要性が浸透すると共に、C言語が普及し始めました。関数という単位でプログラミングをし、関数の中だけで使える変数がある。それだけでもプログラマにとってとてもありがたく、助かりました。

オブジェクトのプライバシー

ハードウェアの進化と共に、ソフトウエアに求められる機能はより多く、より高度になっていきました。当然プログラムは複雑、かつ巨大にならざるを得ません。

この問題解決の一助となったのがオブジェクト指向プログラミングです。

これまで、プログラミングにおけるオブジェクトとは、ただのデータ構造でしかありませんでした。関数などの処理コードがまずあって、そのコードが操作する対象がデータ構造でした。
オブジェクト指向ではこのデータ構造を主体に考えます。そして、コードはデータ構造に従属するという考え方に変わります。つまり、データ構造とコードが非常に密接に関連付けられることになったのです。

その、オブジェクト指向で言うところのオブジェクト、つまり、そのデータ構造に従属する専用の処理コードもひっくるめてのオブジェクトは、これまでの関数という単位よりも高い独立性を確保することができます。

独立性が高いということは、逆に依存度は下がります。その結果、より汎用的で応用範囲も広がり、プログラムの価値も高くなります。

そんな独立性を高めるための機能をご紹介します。(やっとですね!)
C++では、オブジェクトの独立性をどうやって確保するのでしょうか。前回のサンプルコードをもう一度見てみましょう。

コード1

struct Point  // データと処理が一体となっている構造体
{
    int m_x, m_y;
    void set(int x, int y);
};

// Pointのメンバ関数
void Point::set(int x, int y)
{
    m_x = x;    // 構造体メンバm_xに設定
    m_y = y;    // 構造体メンバm_yに設定
}

int main()
{
    Point p;
    p.set( 100, -200 );
    reutrn 0;
}
実はこのコードには問題があります。それは、次のようにメンバ変数を直接どこからでもアクセスできてしまうところです。

コード2

struct Point  // データと処理が一体となっている構造体
{
    int m_x, m_y;
    void set(int x, int y);
};

// Pointのメンバ関数
void Point::set(int x, int y)
{
    m_x = x;    // 構造体メンバm_xに設定
    m_y = y;    // 構造体メンバm_yに設定
}

int main()
{
    Point p;
    p.m_x = 100;  // C言語と同じように直接メンバ変数を変更できてしまう。
    p.m_y = -200;
    reutrn 0;
}
現実的にこのような単純な構造体でしたら直接的な問題はないのかも知れません。
しかし、たとえば分数を表す構造体で、分母に0を設定できないというルールがあった場合はどうでしょうか。

コード3

struct Fraction  // 分数
{
    int      m_numerator;    // 分子
    unsigned m_denominator;  // 分母
    bool   set(int numerator, unsigned denominator);
    double getDouble();
};

// メンバ関数(戻り値は成功/不成功)
bool Fraction::set(int numerator, unsigned denominator)
{
    if(denominator == 0) // 分母に0は設定できない
        return false;    // エラーを返す

    m_numerator   = numerator;
    m_denominator = denominator;

    return true;
}

// これもメンバ関数
double Fraction::getDouble()
{
    // これは分母が0だと困ります
    return (double)m_numerator / (double)m_denominator;
}

int main()
{
    Fraction frt;
    if(! frt.set( -2, 3 ))
        return 1;

    frt.m_denominator = 0;  // ★やばい!

    printf("%f\n", frt.getDouble());  // ここで0除算で落ちるね(;;)

    reutrn 0;
}
せっかく、構造体専用の関数set()で分母が0の場合をエラーにして設定できないようにしていたのに、直接メンバ変数を操作されてしまえば意味がありません。
さて、どうしましょうか。コーディング規約に「メンバ変数を直接操作しないこと!」の一行を追加しましょうか。でも、デバッグ用の特例コードで変数を操作して、うっかりそのコードを本番に残してしまったら? 大量のコードからどうやって見つけますか? ひょっとしたら、m_denominator という変数名は他でも使っているかも知れません。すると、grep機能を使ってもなかなか見つけられませんね。

C++にはオブジェクト指向の隠蔽という考え方を反映させた機能が最初から組み込まれています。コード3にそれを適用したものがコード4です。

コード4

struct Fraction  // 分数
{
private:  // プライベート宣言をして、隠蔽する
    int      m_numerator;    // 分子
    unsigned m_denominator;  // 分母
public:   // 公開することを宣言する
    bool   set(int numerator, unsigned denominator);
    double getDouble();
};

// メンバ関数(戻り値は成功/不成功)
bool Fraction::set(int numerator, unsigned denominator)
{
    if(denominator == 0) // 分母に0は設定できない
        return false;    // エラーを返す

    m_numerator   = numerator;
    m_denominator = denominator;

    return true;
}

// これもメンバ関数
double Fraction::getDouble()
{
    // これは分母が0だと困ります
    return (double)m_numerator / (double)m_denominator;
}

int main()
{
    Fraction frt;
    if(! frt.set( -2, 3 ))
        return 1;

    frt.m_denominator = 0;  // ★★コンパイルエラー!

    printf("%f\n", frt.getDouble());

    reutrn 0;
}
構造体にキーワード「private」と「public」が追加されています。private: 以後は外部からのアクセスができないメンバ(変数も関数も)になります。(もちろんメンバ関数からは自由にアクセスできます)
public: 以後は公開メンバとなります。
その結果、main()関数の中で直接プライベートメンバに値を設定しようとするところでコンパイルエラーになってしまいました。

このように外部に晒したくないメンバを隠蔽することで、不要なアクセスをされる心配がなくなります。アクセスされないことが保証されるわけです。そのことにより構造体がオブジェクトとして独立性を高められる、ということになるのです。

他人の家には、玄関を通してのみ、許可を得て出入りするのと一緒ですね。その家の裏口も窓も開けっぱなしでは、いつ誰が入ってくるか判りません。周辺の住人を信用しているかいないかの問題ではなく、そういった疑いを持たずに済む、ということが重要なのです。

クラスの登場

ここまで、C言語にもある構造体を使って説明してきました。しかし、C++では上記の例のような場合は struct ではなく、class を使うのが一般的です。クラスというのはオブジェクト指向の用語でもあります。実は、C++では struct もクラスです。違いはただ1点だけです。
それは、デフォルトが private か public かの違いだけです。class と struct、どちらがどちらでしょうか。上記のコードを見ていただければ判ると思いますが、class はデフォルトが private、つまり、public: が現れる前は private 扱いになり、struct はその逆です。

コード5

class Fraction  // 分数クラス
{
    int      m_numerator;    // 分子(プライベートメンバ)
    unsigned m_denominator;  // 分母(プライベートメンバ)
public:
    bool   set(int numerator, unsigned denominator);
    double getDouble();
};
違いはそれだけなのですが、慣例として struct を使うのは受動的なデータ構造を扱う場合が多いです。つまり、C言語で使っていたようなデータ構造を示すだけの構造体的なものの場合ですね。

オブジェクト指向の用語でもあるクラスを示す class のデフォルトが private なのは意味があります。公開は必要最小限にして、独立性を高め、プライバシーを強く意識することが信頼性を高めるために大切だからです。

更に、家の玄関を通常は1つにするように、インタフェースはできるだけシンプルにするという意味もあります。

コラム:隠蔽とは?

2018-01-12

Cプログラマのための、
C++オブジェクト指向簡単裏口入門(4)

指向!?

前回、C++でのオブジェクトは実際にはメモリ上の構造に過ぎないことを示しました。では、そんなオブジェクトに対する指向とはどういうことなのでしょうか。単なるメモリ上の構造にしては、少々大げさな表現に思えますよね。

しかし、本当のところ、ここにはパラダイムシフトと言える程の大きな変化が隠されています。にもかかわらず、その変化の基本は非常にシンプルです。

オブジェクト指向ではないC言語では、メモリ構造に対する処理は通常次のようになります。

コード1

// C言語でのメモリ構造操作
typedef struct
{
    int m_x, m_y;
} Point;

void setPoint(Point * pPoint, int x, int y)
{
    pPoint->m_x = x;
    pPoint->m_y = y;
}

int main()
{
    Point p;
    setPoint( &p, 100, -200 );
    reutrn 0;
}
では、次にC++でオブジェクト指向的に書いてみます(C++でもCと同じスタイルで書けてしまうのでこういう表現をしましたが、今後特別な場合を除いてこの説明は省きます)。

コード2

// C++でのメモリ構造操作
struct Point   // 今回はclassではなく、structとしておきます
{
    int m_x, m_y;
    void setPoint(int x, int y);
};

void Point::setPoint(int x, int y)
{
    m_x = x;    // 構造体メンバm_xに設定
    m_y = y;    // 構造体メンバm_yに設定
}

int main()
{
    Point p;
    p.setPoint( 100, -200 );
    reutrn 0;
}
Cにはない書き方がありますね。
1つは、構造体の中に関数 setPoint() のプロトタイプがあることです。これはこの構造体のメンバ関数と呼びます。変数をメンバ変数と言うのと一緒です。そして、そのプロトタイプの実装が Point::setPoint(){} といった関数名で書かれています。Point構造体のメンバ関数なので、「::」(コロン×2)で繋いでメンバ関数であることを表します。
main()の中では、構造体のメンバ変数にアクセスするときと同じように「.」(ドット)を使ってメンバ関数にアクセスし、呼出しています。

コード1も2も同じ機能を果たすプログラムですが、コード2のC++の方はsetPoint()の引数が1つ減っています。これは、メンバ関数の中から同じメンバに対してはグローバル変数のようにアクセスできるので、わざわざパラメータを必要としないためです。

さて、これら2つのプログラムの本質的な違いは何でしょうか。関数を動詞に喩えると、

  • Cの場合、「~をする」
  • C++は、「~が~をする」

と、なります。C++の方には主語があるのです。

あれ、たったそれだけのこと? 目先が違うだけでは? と感じられた人もいるかも知れません。今後、徐々にその意義を理解して頂ければいいのですが、今回は概要を説明しておくことにします。

~が~をする、というのは、オブジェクトと動作が強く結びつけられている、ということでもあります。そうすると何が良くなるのか、ユーザインタフェースを例に説明します。

WindowsにしてもMacにしても、スマートフォンにしてもそうなのですが、GUIにはオブジェクト指向的なコマンドの起動方法が含まれています。

たとえば、Windowsのデスクトップにワープロのドキュメントファイルがあり、それを編集するとき、皆さんはどうされますか? 多くの人はそのファイルをダブルクリックしますね。すると、結びつけられているワープロソフトがファイルを開いた状態で立ち上がります。ファイルが表計算ソフトのものであれば、表計算ソフトが同様に起動します。
ダブルクリックではなく、マウスの右クリックでコンテキストメニューを開き、「編集する」というコマンドを選んでも同じ動作が可能です。また、右クリックでは印刷コマンドとか、プロパティを表示するといった、そのファイルで可能な別のコマンドも実行できるようになっています。

この動作はユーザにとって極めて自然です。もし、GUIにおけるオブジェクト指向がなかったらどうでしょうか。編集するときは目の前にファイルが見えていても、一旦ワープロソフトを起動し、ワープロソフトからファイルを探してオープンする操作をしなければなりません。

世の中のコンピュータがGUIになる以前は、全てコマンドプロンプトからプログラムを起動しなければなりませんでした。テキストを編集するコマンドが edt で、編集対象のファイルが test.txt であるなら、

    > edt  c:\myfiles\test.txt

等と入力しなければなりませんでした。コマンドも、対象ファイルもどちらも覚えておかなくてはならなかったのです。

現在主流のGUIは単にマウスを使ってグラフィカルに操作するだけでなく、ファイルとコマンドの結びつきが定義されていて、そのファイルに対して何ができるのかといったことをユーザが考えたり、予め知らなくてもいいようになっています。
オブジェクト(この場合はファイル)と動作(コマンドやアプリケーション)が密接に関連付けられている、という意味ではオブジェクト指向的なユーザインタフェースであると言えるのです。

プログラミングに戻りましょう。

上記コード1では、Point構造体とそれを扱う関数との間に直接の関連付けはありません。Point構造体だけ見ても、それに対するどんな処理が存在するのか不明です。不便ですね。上記のCUIのときと似た不便さがあります。
逆に、コード2ではそれが明らかで、これもGUIの例になぞらえることができます。

関数の側から見てみると、コード1ではsetPoint()という関数名はグローバル定義にするしかありません。なので、他の目的でも同じ関数名が必要になった場合、名前がぶつかってしまいます。なので、汎用的で意味の広い関数名は使いにくくなります。不便ですね。
コード2の場合、同じメンバでなければ(同じメンバでも引数が異なれば可能)、同じ関数名がいくつあっても問題ありません。なので、シンプルで判りやすい関数名を使うことができます。
ところで、コード1での関数名はPoint構造体に値を設定する、という意味でsetPoint()という関数名にしてありますが、コード2は関連付けが明らかなので、あえて関数名にPointを含める必要はありません。なので、この場合は単にset()とした方が良いでしょう(コード3参照)。

コード3

// C++でのメモリ構造操作
struct Point   // 今回はclassではなく、structとしておきます
{
    int m_x, m_y;
    void set(int x, int y);
};

void Point::set(int x, int y)
{
    m_x = x;    // 構造体メンバm_xに設定
    m_y = y;    // 構造体メンバm_yに設定
}

int main()
{
    Point p;
    p.set( 100, -200 );
    reutrn 0;
}

ここまでをまとめると、
オブジェクトを主体に考えること(つまりオブジェクト指向)で、オブジェクトに対して何ができるのかがはっきりする。処理、という観点においても、オブジェクトが結びついていることで何に対する処理なのかが明確になる。ということになるでしょうか。

そして、この判りやすさは使うときだけではなく、設計やコーディング時にも享受できるのですが、その点については今後徐々に理解して頂けると思います。


Cプログラマのための、
C++オブジェクト指向簡単裏口入門(3)

オブジェクト!?

これまで、散々オブジェクト指向って聞かされてきたけれど、そのオブジェクトとは何なの? という疑問をお持ちでしょうか。それは、平たく言えば「もの」となります。C++ではこれをクラスというものを使って構築&表現します。

おっと、既に話が飛躍したかも知れません。

オブジェクト指向というのは、単にプログラミング以外にも、システムの概要的な設計の段階からその考え方を適用可能なので、一概にその「もの」が実際には何であるか、とは言い切れないところがあります。しかし、この記事はC++プログラミングに限定するので、それはメモリ上のある構造を指しているに過ぎない、と言えます。

Cでは構造体に相当する、クラスというものがC++にはあります。キーワードもそのものズバリ、classです。そして、構造体を示すstructも、実はC++ではクラスなのです。違いはほとんどありません。ある意味、全く違わないとも言えます。(小さな違いについてはいずれまた)

クラスそのものはオブジェクトではありません。オブジェクトの設計図です。これをインスタンス化したものがオブジェクトです。「インスタンス化」という言葉は是非ここで覚えてしまってください。次のコードのように使用します。

コード1

// まずはクラスの定義
class MyClass
{
    int member;
};

void func()
{
    MyClass object;    // MyClassクラスをobjectという名前でインスタンス化
}
このやり方は実はCでもありましたね。Cでは構造体になりますが、そっくりなコードになります。

コード2

// 構造体の定義(C言語)
typedef struct
{
    int member;
} MyStruct;

void func()
{
    MyStruct object;  // これも実はインスタンス化
}
ちなみに、C++ではsturctもclassと同様、typedefは不要です。
是非、C++なのにstructにtypedefを使うような大人にならないでくださいね!?(何故か使う人が多い)

コード3

// 構造体の定義(C++)
struct MyStruct  // structでも実際にはクラス。構造体の上位互換性がある。
{
    int member;
};

void func()
{
    MyStruct object;
}

ここまでをまとめると、オブジェクト指向のオブジェクトの設計図がクラス、そして、そこからインスタンス化したものがオブジェクトになります。
なので、C++でオブジェクトとは、メモリ上の構造でしかない、と覚えておいてほぼ間違いはありません。

2018-01-10

Cプログラマのための、
C++オブジェクト指向簡単裏口入門(2)

なぜ、オブジェクト指向なのか?

ソフトウエアの品質を上げようとすると、自然にオブジェクト指向にたどり着いた、と、僕自身は感じています。
このシリーズを通じて、読者の方にも徐々に感じていただけたらと願っています。なのでこの問いの回答を最初に説明するつもりはありません。

ただし、C++の機能を知っていく中でオブジェクト指向の必然性を感じていただくためのヒントとして、ソフトウエアの品質について少し説明させてください。

ソフトウエアの品質とは


ソフトウエアの品質とは、主に
  • 機能性
  • 安定性
  • 保守性
  • コストパフォーマンス
から成っています。
もちろん、それぞれの要素は完全に独立しているわけではなく、相互に強く関連し合っています。

[機能性]
コーディング前のフェーズにはこれを大きく左右する要素が含まれますが、コーディングでは主に処理速度のことを指します。
場合によっては安定性や保守性とトレードオフの関係になります。

[安定性]
動作の安定性のことで、バグが含まれないことが求められます。
安定性と機能性は相反する場合がありますが、現在はハードウェアの性能が高くなった分を安定性に振り分ける余裕があるので、多くのプログラミングでこれがせめぎ合うケースは少ないと思われます。もちろん、リソースに余裕のないハードウェアをターゲットにしたり、実効速度に関する要求仕様が非常にシビアなジャンルもあり、全てが容易に両立できるわけではありません。

[保守性]
機能追加やバグ修正のしやすさです。他の要素に比べて見落とされがちですが、非常に重要な要素です。
機能性とはトレードオフの関係になる場合もありますが、安定性とはトレードオンの関係になる場合が多いと思われます。

[コストパフォーマンス]
主に開発効率のことです。
上記3要素が全て揃っても、システム開発に莫大なコストがかかってしまえば現実的には成立できず、システムは存在できなくなります。技術者であってもその点を意識し、開発効率を常に考慮する必要があります。

ソフトウエアの生存期間をトータルで考えた場合、特に保守性、および安定性とはトレードオンの関係と言えます。機能性とは間接的にトレードオフの関係になります。
更に、システム開発において1から10までを毎回全て新規開発する状況は好ましくありません。独立性が高く、汎用性のある部分をライブラリ化して無駄な工数を削減することはとても重要です。つまり、1つのシステムだけではなく、社内的な開発体制にまで視野を広げる必要があるということになります。

オブジェクト指向と品質の関係


ここでは具体的な説明は避けますが、概要だけお伝えしておきます。
オブジェクト指向は品質の要素のうち、特に「安定性」、「保守性」、「コストパフォーマンス」に大きく寄与します。
「機能性」については、実行速度の点でややマイナスになる可能性はありますが、少し俯瞰して見ると、その他の要素が向上することで余力が生まれ、それまでできなかったことができるようになるなどして、むしろプラスに作用する場合も多いはずです。

ソフトウエアの品質を意識する


ソフトウエアの品質を常に意識しながら読み進めてください。そして、実際のコーディング時にも常に意識することが大切です。

コーディングのしやすさは開発効率に寄与しますが、場合によってはあまり目先のことに囚われるとトータル的には効率を落としてしまうことがあります。システムの開発サイクル全体を意識して判断することが求められます。


2018-01-09

Cプログラマのための、
C++オブジェクト指向簡単裏口入門(1)

はじめに


「C++を学ぶ前に、まずはオブジェクト指向を学べ。」

この言葉を鵜呑みにして何やら詳しそうなオブジェクト指向の本を買ってみても、モデルがなんちゃらとか、なじみのない用語が登場していきなり疎外感に見舞われます。仕方なく章を飛ばすけれど、やはりC言語では比較的なじみのない業務系アプリケーションの設計方法が例として採り上げられていて、鬱陶しいやらめんどくさいやら。

ご立派なオブジェクト指向の本(聖書)には妙に遠回りさせられてしまうし、ひょっとしたら制御系をやっている人にはあまり関係が無いんじゃないか、等と都合の良い理由を付けて諦めてしまいます。

要は、オブジェクト指向の敷居は妙に高い。という、気にさせられてしまうわけですね。
こんな環境がオブジェクト指向ディバイド(リンク先参照)を産んでしまうのでしょうか。

きっと、その要因はもっといろいろあると思いますが、まずはそんなことは忘れて、C++という言語を使って、より効率よく、より品質の高いプログラミングをする、ということを目標に、まずはできることからやってみませんか。
もちろん、これまでオブジェクト指向を学ぼうとしたことがなく、上記のような経験さえないという方も大歓迎です。

冒頭の言葉を言い換えるなら、

「C++を学ぶなら、オブジェクト指向もついでに学べ。」

と、なります。

もし、過去BLOGの Cプログラマのための、C++簡単裏道アプローチ を読んでいないなら、できれば先に目を通しておいてください。
そちらの記事はオブジェクト指向を避けてC++のメリットをお伝えするものでしたが、やはりそれには限界がありました。C++の本領を発揮するにはやはりオブジェクト指向を避けては通れなくなり、そこで今回の記事を書くことにしたのです。

本編は(2)から始めます。