中級編

中級編

さて,いよいよこのページの本命となる部分です.
これはC++によっていかに良いプログラムを書くかという思想とその設計方法について説明します.
このページは「Effective C++」と「More Effective C++」の著書の一部を説明していきます.

Contents

#defineよりconstを使え!

Effective C++:第1章第2項「#defineより,const, enum, inlineを使おう」

さて,みなさんはよく次のような#defineの使い方をすると思います.
#define NUM 10  // 定数の定義
このようにdefineを利用して定数を定義しています.
しかし,このdefineの使い方にはいろいろ問題があります.

まず最初に,defineを使って定数を定義するのは100歩譲ってよしとするとした場合の問題点をご説明します.
#deifneは知っての通り
,マクロ命令の代表的な1つです.
しかし,このdefineによる定数の定義は,C言語の変数のように定義できるのではなく,あくまで,文字列の置き換えである事に注意してください.
つまり次のような場合,
int a[NUM];
は,コンパイラには
int a[10];
と認識されています.
そこでまず注意したいのは,次のような場合です.
class NUMBER;  // NUMBERクラスの定義
このようなクラスをもし定義した場合,どうなるでしょうか?
答えは簡単で,#defineは一致する文字列を置き換えるので,クラスはコンパイラには
class 10BER;
と認識され,コンパイルエラーとなります.

また,このようなミスで一番怖いのが次のような場合です.
#define NUM 10      // NUMを定義
#define MORE NUM+5 // MOREを式として定義. 15としているつもり
この例では#defineをつかって式を定義しています.
さてこれらの定義をつかって次のような式を書きます.
int a=MORE*2;    // a=(NUM+5)*2=30としたつもり
しかし,この変数aはコンパイラには
int a=10+5*2;    // =20となっている
と認識されています.これでは,プログラマの意図した通りの挙動を示しません.
この問題の一番怖いところは,コンパイルエラーにならなバグであることです.
defineのルールを忘れているため,式がおかしなことになるのです.
この問題の解決方法は単純で
#define MORE (NUM+5)  // 括弧付きにする
と括弧を付ければ良いのです.これで先程の式は
int a=MORE*2  // a=(NUM+5)*2となる
問題がなくなります.
つまり,#defineを使う場合,よほどの事がない限り括弧付きで定義するほうが良いでしょう.

さて,上の問題はあくまで#defineを使う場合の話であり,通常,defineで変数を定義することはやりません.
それは,グローバル変数を定義するのよりたちが悪く,プログラムのカプセル化の妨げになるからです.
また,先程の例でもあげたように,
#difine NUM 10
...
class NUMBER;  // コンパイルの時点で10BERとなる
の用な定義をした場合,コンパイルエラーとなり,おかしな事になります.
さて,この問題の一番の厄介なところは,コンパイルエラーのメッセージが「10BER」と いう形で出て来るため,エラーの意味が分かりづらいというところです.

さてこれら諸々の問題を引き起こさないためにはどうするのが良いのでしょうか?

答えは簡単で,constをつけた通常の変数にしてしまえば良いのです.
つまり,
const int Num=10;   // constがついているため,変数は変更不可
とすればよいのです.
また,クラス内のみで使いたい変数は
class Sample{...
static const int Num=10;
...
};
と定義すればよいのです.
ただし,コンパイラのバージョンによっては上のコードではエラーとなる場合もあります.
クラスの定義内で変数の初期化ができない場合があるためです.
そのときは次のように書けば大丈夫です.
class Sample{...
static const int Num;       // staticなクラス定数の宣言のみ
...                // これはヘッダファイルに記述される
};

const int Sample::Num=10;   // staticなクラス定数の定義
                            // これはソースファイル内に記述

#defineにはスコープという概念がないので
,どのプログラムからも参照してしまう事ができてしまいます.
それによって,プログラムのカプセル化を損ねてしまいます.
しかし,上のようにstatic constにした変数を使えば,クラス内のみで有効なprivate変数としても定義できるようになり,オブジェクト指向としては非常に有効な手段と言えます.

みなさんも,defineによる定数を使わないで,constを使った変数を利用するようにしていきましょう.





できる限りconstを使え!

Effective C++:第1章第3項「可能ならいつでもconstを使おう」

引き続き,constを使ったお話です.
#defineの代わりにconstを使う事で定数が作れる事を前節で説明しました

ここで重要なのは,当たり前の話ですがconstで宣言した変数は変更できないという事です.
この性質を利用してうまくプログラムを書く方法について説明したいと思います.

まず基本的な話ですが,constを使うと次のように配列の中身を固定する事ができます.

const char[10]="abcde";
char[0]='c'  // コンパイルエラー
             // constな配列なので,中身を変更できない

ところで,constを使った方が良い場合をいくつか紹介しましたが,それらは必ずしもconstが必要という訳ではありません.
たとえば,定数を作るのにconstを使っていますが,これは通常の変数に値を入れておいても,プログラム的には動作します.
ただ単に,その変数に値を書き込まないように,ユーザや設計者が気をつければ済みます.

しかし,気をつけて書かないといけないクラスやプログラムはダメなプログラムです.
ユーザが間違った使い方ができないようにするようなプログラムが良いプログラムと言うことなのです.
定数を作るのにconstを使ったのは,間違って定数を変更できないようにするためと,これは定数だという事を明示的に示すためのものなのです.

さて,この明示的に示すという事も実は重要な要素です.
たとえば,下のような関数があるとします.
void FuncA(char* arr);
void FuncB(const char* arr);
この関数は文字列ポインタを引数にしていると考えてください.
FuncAの引数は配列のポインタであり,FuncBは引数のポインタにconstがついています.
まずFuncAを次のように実装してみます.

void FuncA(char* arr){
    ...
    arr[0]='J';
}

char* str="Hello";   // 配列に"Hello"の文字列を代入
FuncA(str);     // この時点で文字列str="Jello"となる
...

これはポインタを関数の引数にした場合にしばしばおこる厄介な問題です. 
関数の引数にした変数の中身は,呼び出し側には影響しないと思って使っている場合で,厄介なバグを引き起こします.

さて,FuncBで次のような実装をしたらどうなるでしょうか?
void FuncB(const char* arr){
    ...
    arr[0]='J'; // 引数にconstがついているので,中身を変更しようするとコンパイルエラー
}
答えはコンパイルできません.
つまり関数の引数にconstをつけたポインタを渡した時点で,その引数ポインタの中身は変更できなくなりますし,変更していない事を示しているのです.
このように,constを明示的につける事で,引数ポインタを変更していませんという事をユーザに伝えているのです.

つまり,関数にポインタを渡す場合,中身を変える必要がない場合にはconstをつけておくべきであり,その方が親切という事になります.
つまりconstがつけれるのであれば,できるだけつけてあげてください.

また,constは関数の引数だけではなく,関数そのものにもつける事ができます.
int GetValue() const{    return value;}
この関数はクラス内のprivate変数を返す関数だと思ってください.
この関数全体で値を何かに代入していないのでconstをつける事が可能となります.
つまり例えば
int GetValue() const{ value=1;   return value;}
と記述した場合,コンパイルエラーとなります.

もちろん何回も言うように,constをつけなくてもプログラムは成り立ちます.
しかし,つけてあげた方がその関数,クラスを使うユーザはそれらの仕様を正しく理解する事ができるようになります.
よって,constはできる限りつけた方が良いのです.


勝手に作られる関数を知ろう!

Effective C++:第2章第5項「C++が自動で書き,自動で呼び出す関数を知ろう」
Effective C++:第2章第6項「コンパイラが自動生成する事を望まない関数は,使用を禁止しよう」

次のようなクラスがあるとします.
class Sample{
private:
public:
    Sample(){}
     Sample(){}
};
中身が何にもないクラスです.
このようなクラスが定義された場合,呼び出せるクラスのメンバ関数はなんでしょうか?
実はコンストラクタだけではなく,コピーコンストラクタも呼び出す事ができるのです.
Sample smp;
Sample csmp(smp);
さて,コピーコンストラクタの定義はしていないのに,なぜ使えるのでしょうか?
それは,クラスにコピーコンストラクタが定義されていない場合,勝手にコンパイラが定義してしまうのです.
また,次の場合もコンパイラが勝手に定義します.
class Sample{
private:
public:
    Sample(int a){}    // 引数有りコンストラクタ
     Sample(){}
};
この場合,デフォルトコンストラクタとコピーコンストラクタが勝手に定義されています.
Sample();                // デフォルトコンストラクタ
Sample(const Sample&);   // コピーコンストラクタ

インタフェースは間違えにくいものにしよう

Effective C++:第4章第18項「インタフェースは,正しく使うときには使いやすく,間違った使い方では使いにくいものにしよう」


オブジェクトの引数にはconst&をつけよう

Effective C++:第4章第20項「値渡しよりconst参照渡しを使おう」
さて本題に入りたいと思います.
クラスや構造体といったオブジェクトを引数とする関数を考えます.

struct Sample{
    int x,y;
}

データメンバはprivate宣言だ

Effective C++:第4章第22項「データメンバはprivate宣言しよう」

変数を定義する場所

Effective C++:第5章第26項「変数の定義は可能な限り先延ばししよう」

inlineををよく理解しよう

Effective C++:第5章第30項「インラインをよく理解しよう」


最終更新:2010年12月03日 13:02
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。