さて、いよいよ「コンストラクタに特性を与える」という部分に入っていこうと思います。
|
この2つを征する者がコンストラクタを征す |
さて、デフォルトコンストラクタとコピーコンストラクタは「自動生成」されることが分かったと思います。逆に言うと、これは「他の型の引数を取るコンストラクタは自分で作らない限り生成されない」ということになります。
つまり、デフォルトコンストラクタとコピーコンストラクタのふたつさえ押さえておけば、そのクラスのコンストラクタはすべて征したことになります。逆に言うと、手抜きしてこのふたつのコンストラクタを作らないでおくと「穴ができた」と言えます。 もしコンストラクタに「コンパイラが生成するものと同じ機能」を持たせるのであれば、それでいいでしょう。でも、クラスに「厳密な特性」を持たせる場合、それはセキュリティーホールになりかねません。「特性」をクラスに持たせる場合には、デフォルトコンストラクタとコピーコンストラクタは必要不可欠ということになります。 で、その「厳密な特性」についてこれから解説していきます。これこそが、このページの本題です。 |
protected 指定する |
すべてのコンストラクタを protected に指定するとどうなるでしょう。
|
class CTest { protected: // デフォルトコンストラクタ。 CTest() { TRACE0( "デフォルト\n" ); } // コピーコンストラクタ。 CTest( const CTest &p_rcTest ) { TRACE0( "コピー( CTest )\n" ); } }; void Test() { // CTest cTest; //コンパイルエラー! }
以上のようにコンストラクタを protected にすると、 private と同じようにクラスの外からアクセスできませんから、このクラスのインスタンスは作成できません。
作成できないクラスなんて意味がないように思えますが、このクラスにはちゃんと意味があります。 protected には private と違う点がひとつあります。それは「 public 継承したクラスのメンバ関数からは呼び出すことができる」ということです(土橋さんありがとう!)。 つまり、次のようなことはできるということです。 |
class CTest2 : public CTest //さっきのクラスから public 継承。 {}; void Test() { // CTest cTest; //コンパイルエラー! だけど、 CTest2 cTest; //こっちは通ります。 }
このように、すべてのコンストラクタを protected にすることで、クラスを「継承元としてのみ使える」ということになります。同じようなことは「純粋仮想関数」を作ることでもできますが、こちらの方がより明示的でしょう。
|
いずれかを private に指定する |
今度はデフォルトコンストラクタかコピーコンストラクタを private に指定してみましょう。
|
class CTest { public: // デフォルトコンストラクタ。 CTest() { TRACE0( "デフォルト\n" ); } private: //プライベート // コピーコンストラクタ。 CTest( const CTest &p_rcTest ) { TRACE0( "コピー( CTest )\n" ); } }; void Test() { CTest cTest; //これは大丈夫。 // CTest cTest2( cTest ); //これはコンパイルエラー。 }
上の例ではコピーコンストラクタを private に指定しています。そうすれば、コピーコンストラクタを呼び出そうとした場合コンパイルエラーになります。つまりコピーコンストラクタを呼び出せなくしたというわけです。
同じ事がデフォルトコンストラクタにもできます。他の引数付きコンストラクタには意味がありません(作らなければいいだけ)。 コンストラクタを「封印」することの意味は大きいです。 たとえば「何かを参照するクラス」の場合、ひとつの参照先をいくつものクラスが参照すると混乱する場合があります。そういうことをクラス側で禁止したい場合、コピーコンストラクタを封印することで解決できます。 また、「必ず NULL 以外のポインタを持ちたい(参照はダメ)」という場合にはデフォルトコンストラクタを封印することで解決できます。 |
ふたつとも private に指定する |
以上ふたつを見ておそらく思うことは「まさかふたつとも private に指定したクラスに使い道はないでしょう」だと思います。ところがあるんです。キーは friend 指定です。
|
class CTest { private: // デフォルトコンストラクタ。 CTest() { TRACE0( "デフォルト\n" ); } // コピーコンストラクタ。 CTest( const CTest &p_rcTest ) { TRACE0( "コピー( CTest )\n" ); } friend void Test2(); // friend 指定。 friend class CTest2; //同じく。 }; void Test() { // CTest cTest; //これはコンパイルエラー。 } void Test2() //friend 指定されてるから、 { CTest cTest; //これは大丈夫。 } class CTest2 //firend 指定されてるから、 { CTest m_cTest; //メンバ変数にできます。 }; void Test3() { CTest2 cTest; }
このように firend 指定したクラスや関数では、 private 指定は public 指定と見なされます。そのため、これらの関数やクラスではインスタンスを作成できるというわけです。
つまりデフォルトコンストラクタとコピーコンストラクタを private に指定することで、 firend 指定した特定のクラスや関数のみでインスタンス化できるというわけです。ただしこの場合、 private メンバ変数などにも直接アクセスできてしまうので、それが困る場合にはさらに継承などをする必要があると思います。 |
オブジェクト指向 |
以上が「アクセス指定によるクラスの特性化」です。このように、コンストラクタを private や protected にすることで、クラスの性格ががらっと変わります。
これは「オブジェクト指向プログラミング」の特徴のひとつです。C言語の場合、変数は単なるデータでした。そのため、どう扱うかは関数の自由でした。でもC++言語の場合、クラスに以上のような様々な性格を持たせることができます。この機能のおかげで、データを関数の自由にさせずに済みます。その結果、より安全で強固、かつ柔軟なデータ処理も可能になると言うわけです。 |
もうちょっと続く |
さて、コンストラクタ編は後編へと続きます。
これまではたったひとつのクラスのコンストラクタのみを考えてきました。でも、継承を行ったりメンバ変数を持つことで、自分以外の様々なクラスのコンストラクタを呼び出すことになります。 後編はコンストラクタ編のまとめとして、この点について見ていきましょう。 |
(C)KAB-studio 1999 ALL RIGHTS RESERVED. |