SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

匠のキャリアへ

C++による開発で陥りやすい問題点の検証

言語仕様の基礎を修得することの重要性


  • X ポスト
  • このエントリーをはてなブックマークに追加

オーバーロードとオーバーライド

 ところで、オーバーロードとオーバーライド、この2つの違いを説明できるでしょうか。分かっていても、どちらがどちらか混乱してしまう人も少なくないのではないかと思います。念のため整理すると、

  • オーバーロード:
  • 引数の数あるいはデータ型が異なる同名の関数を多重定義すること。
  • オーバーライド:
  • クラスの継承において、基底クラスにより定義されているメンバ関数を、関数名・戻り値・引数の数・データ型が一致するメンバ関数を派生クラスで定義し、置き換えること。

 となります。さて、次はオーバーロードとオーバーライドに関するケースです。まず、オーバーロードについて簡単な例をご覧ください。

#include <stdio.h>

class Parm
{
public:
  void print(double x) { printf("%f\n", x); }
  void print(char *x) { printf("%s\n", x); }
};

int main(void)
{
  Parm parm;
  parm.print(2.3); /* A */
  parm.print("2.3");   /* C */

  return 0;
}
実行結果
2.300000
2.3

 実行結果から分かるように、コンパイラは引数の型を判断し、Parmクラスのどちらのメンバ関数を呼び出すかを決定します。

 次にオーバーライドの簡単な例を示します。

#include <stdio.h>

class Parm
{
public:
  virtual void parm(int x){ printf("parm=%d\n", x); }
};

class SubParm : public Parm
{
public:
  void parm(int x){ printf("Ah, my!"); } //引数とは関係ない文字列を表示
};

int main(void)
{
  Parm *sp = new SubParm;
  sp->parm(2);  //SubParm::parm関数が呼び出される
  delete sp;
}
実行結果
Ah, my!

 このように、コンパイラはspの実体がどの型で作られているかを判断して、呼び出すメンバ関数を決定します。

 ところで、オーバーロードとオーバーライドが同時に利用される場合はどうでしょうか。以下の例をご覧ください。

class Parm
{
public:
    virtual void parm(int);     // A
    virtual void parm(double);  // B
};

class SubParm : public Parm
{
public:
    void parm(int);             // C
};

////
//// 各メンバ関数の実装は省略
////

int main(void)
{
    SubParm *sub = new SubParm;
    Parm *sp = sub;

    sp->parm(2.3);   // D
    sub->parm(2.3);  // E
}

 このようなクラス構成では、メンバ関数のオーバーロードとオーバーライドが同時利用されることになってしまいます。main関数のコメントDではコメントBの関数が呼ばれますが、コメントEではオーバーロードによって基底クラスの仮想関数を隠匿してしまうため、コメントCの関数が呼ばれ、double型の引数はint型の引数として渡されてしまいます。この動作は作成者の意図に反したものなのではないかと想像できますが、もし作成者が意図したものだとすると、このソースコードを読む人を混乱させる原因になるでしょう。

 この問題を解決するには、オーバーロードされた全ての仮想関数を派生クラスでオーバーライドさせたり、非仮想関数をオーバーロードして関数名の異なる仮想関数を呼び出すようにすれば良いでしょう。しかし、大事なのはこのような解決方法ではなく、オーバーロードとオーバーライドについてきちんと理解し、上記のような問題に陥らないようにすることであることは言うまでもありません。

まとめ

 いくつかの例を挙げてC++の罠について述べてきましたが、いかがだったでしょうか。冒頭でも述べた通り、通常業務でC++を使用しているエンジニアの方の中には、退屈なものだと感じた方もいらっしゃるかもしれません。しかし、そのようなエンジニアの方こそ、ここで挙げたような例に頭を抱えた経験があるのではないでしょうか。現実問題として、言語仕様の基礎を身につけずに目の前の開発に追われ続けてしまうプログラマは少なくありません。もちろん、ソースコードレビュー、インスペクション、ペアプログラミングなどを駆使してこのような不具合を潰していくような活動は必要ですが、その前段階、つまりエンジニア個人の能力を高めていく努力が不可欠であり、その王道は基礎をしっかり身に付けることに違いありません。この記事が、読者の皆様に基礎の重要性を再認識していただくきっかけとなることを願って止みません。

 
 
修正履歴

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
匠のキャリアへ連載記事一覧

もっと読む

この記事の著者

阿部 学(アベ マナブ)

PHS実機評価、C/Sアプリケーション開発、Javaチップ開発等を経て、2002年6月にエイムネクスト(株)に入社。現在、FA機器の通信処理改善プロジェクト、Windowsの通信ミドルウェアを組み込み機器に移植するプロジェクトに従事。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/276 2008/08/28 18:12

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング