コンスとラクタ | ゲームプログラマ志望が福岡で叫ぶ 『絶望』

ゲームプログラマ志望が福岡で叫ぶ 『絶望』

プログラマーになりたい!!!!! あ、風のうわさで聞いた最近若者で流行っているトゥイッターなるものを始めてみました (・ト・) @toshi_desu_yo

はい。
ちゃんとプログラムのことも書いていこうと思います。

本日はコンストラクタ。
c++ にかぎらずオブジェクト思考プログラミングではおなじみですね。 はい。

自分のクラス名と同じ名前の戻り値なしの関数を記述 → コンストラクタになります。


Class TestA
{
public:
// コンストラクタ
TestA() {}
}



クラスが作成された時に呼び出されるコンストラクタ。
正確にはクラスの実態が作られた時に呼び出されます。

つまり

TestA* pTestA;




この状態ではまだコンストラクタが呼ばれません。
入れ物を作っただけで実態はまだ作成してないからですね。

TestA* pTestA = new TestA;


実態を作ってやることで初めてコンストラクタが呼ばれます。

実態を作ってあげる事をインスタンス化といいます。

クラスの中にあるメンバ変数をインスタンス変数と呼ぶのは実態が作成された変数という意味があるからでしょうかね。



コンストラクタ
コピーコンストラクタ

があり、特殊な初期化方法で、

代入演算子



がある。
コンストラクタは上に記述してある通り、
実態を作成した時に呼び出される関数( 引数は幾らでも書いて良い )

コピーコンストラクタと代入演算子は引数が1つしかなく、
その引数も自分の参照を渡してあげる形になります。



【コンストラクタ】

コンストラクタは実態が作成された時に自動的に呼び出される関数。
つまり初期化に向いているわけですね。一々

void Initialize()

という初期化関数を別に作成しなくても良くなるということです。

もし、実態を作成したと同時に変数の初期化も行いたいならば、普通の関数のように

class TestA
{
private:
    /** メンバ変数 **/
    int mID;
    int* mpNumber;

public:
    // コンストラクタ
    TestA( int id, int* pNumber )
    {
mID = id;
  mpNumber = pNumber;  // アドレスの代入
    }
};

とすれば良いです。


呼び出すときは


// int型のポインタ
int* pNumber = new int;  
*pNumber = 10;

TestA test1( 1, pNumber );

または、

TestA* pTest1 = new TestA( 1, pNumber );

と  ( )  を付けて、先頭から順番に型にあった引数を記述してあげるとおk。

今のままだと、必ず引数を記述しなければクラスを作成できないので、引数の要らないコンストラクタも作っちゃいましょう。


class TestA
{
private:
    /** メンバ変数 **/
    int mID;
    int* mpNumber;

public:
    // コンストラクタ( 引数なし )
    TestA()
    {
        mID = 0;
        mpNumber = NULL;

    }

    // コンストラクタ( 引数あり )
    TestA( int id, int* pNumber )
    {
mID = id;
   mpNumber = pNumber;  // アドレスの代入
    }
};

これで引数有り無し両方対応になります。

int* pNumber = new int;
TestA test1;         // 引数なし
TestA test2( 1, pNumber );    // 引数あり


  ↓

test1.mID は 0
test1.mpNumber は NULL

test2.mID は 1
test2.mpNumber は pNumber のアドレスが入っている


他にも


class TestA
{
private:
    /** メンバ変数 **/
    int mID;
    int* mpNumber;

public:
    // コンストラクタ( 引数なし )
    TestA()
        : mID( 0 )
        , mpNumber( NULL )

    {}

    // コンストラクタ( 引数あり )
    TestA( int id, int* pNumber )
: mID( id )
, mpNumber( pNumber )
    {}
};

としても、最初に書いたクラスと全く同じ動きをします。メンバ変数に引数の値が入るわけですね。

こちらのほうが 初期化しとるで!奥さん!!って感じがするのではないでしょうか。

最初のクラスは少し無駄があります。
そちらは最後のほうで! ( 特に伸ばす意味は無い )




【コピーコンストラクタ】

class TestA
{
public:
    // コンストラクタ
    TestA() {}

    // コピーコンストラクタ 
   TestA( const TestA& cpy )   { /** 処理を自分で記述 **/ }
};


通常の使い方としては引数のクラスからただ変数をコピーするために使用します。

class TestA
{
private:
    /** メンバ変数 **/
    int mID;
    int mNumber;

public:
    // コンストラクタ
    TestA( int id, int number )
           : mID( id ), mNumber( number )
    {}

    // コピーコンストラクタ
    TestA( const TestA& cpy )
    {
// 代入して変数をコピー
    mID = cpy.mID;
    mNumber = cpy.mNumber;
    }
};

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

TestA test1( 1, 10 );
TestA test2( 2, 100 );

test2( test1 );  // コピーコンストラクタが呼び出される

よって、test1 と test2 の変数の値は一緒である。


または、


// 何かしら処理をする関数
void Func( TestA a ) { ,,, }
~~~~~~~~~~~~~~~~~~~~~~~~~

TestA testA( 1, 10 );

// 関数呼び出し
Func( testA );



これもFunc関数に入る時にコピーコンストラクタが発生します。


以上。
すごく単純です... ///


他に気をつけるところは const を書かなければエラーになる所。
コピー元が変更されないように記述しないといけない。


実は本日, XCodeでc++ を記述している時にこれを忘れていて、 

『コンパイルが通らねえ!! ぜってぇ間違ってねぇのにふざけんじゃねーぞ!!Appleぶっ壊れたか!? カチカチカチカチカチ!!!! ← コンパイルボタンを連打する音 』


と自分のミスなのにXCodeを散々馬鹿にして途中でこれに気づき、XCodeにブラックトンジ家に伝わる108の土下座の1つバックトゥーゲザーフューチャーをした経緯があったので、
二度とこんなツマラナイミスでXCodeちゃんを怒らせないようこの記事を書こうと思ったわけでありまする(´・ω・`)

いや、ほんと申し訳ない。。。あまつさえ天下のApple様をも愚弄してもうた。。



【代入演算子】

コピーコンストラクタとほぼ同じです。

class TestA
{
private:
    /** メンバ変数 **/
    int mID;
    int mNumber;

public:
    // コンストラクタ
    TestA( int id, int number )
           : mID( id ), mNumber( number )
    {}

    // 代入演算子
    void operator = ( const MyVector& cpy )
    {
// 変数をコピー
     mID = cpy.mID;
    mNumber = cpy.mNumber;
    }
};



動きとしては

TestA test1( 1, 10 );
TestA test2( 2, 100 );

test2 = test1;     // 代入演算子が呼び出される。

  ↓

test2.mID は 1
test2.mNumber は 10 が入っている。



operator = というのは = の機能を自分で書いちゃう。 ってものです。
他にも +, -, *, / ....... と様々な機能を自分で記述できますが、今は特に要らないです。





普通にクラスを使用していて、コピーコンストラクタや代入演算子を書いていなくてもクラスを関数に渡したり = で代入処理をしていた場面があると思います。


何故かというと、自分でコンストラクタ、コピーコンストラクタ、代入演算子を記述していないと

コンパイラが勝手に上記3つを作成してくれるからです。
しかもコピー系はデフォルトで中身をコピーしてくれます。

なんという便利機能... ( ゚д゚ )
脱帽やでぇ。。。  はい、僕はただのめんどくさがり屋です。



え? なら自分で書かなくてい~じゃーん!! 
クラスめんどくせーからいーやー! ちゃー!!

となります。

確かに普通に使用する分では良いですが、メンバ変数にポインタを書いていると話は別になります。


【例えが悪いけど許して例】

class TestA
{
private:
    /** メンバ変数 **/
    int* mpNumber;

public:
   // コンストラクタ
   TestA()
   {
        mpNumber = new int;
        *mpNumber = 1;
   }

   // デストラクタ( delete時に呼び出される関数 )
   ~TestA()
   {
       // ポインタを削除。
  delete mpNumber;
   }
};


デストラクタで引数で貰ってきたポインタ変数を削除する記述を書くとします。
デストラクタは自分が削除される時に自動で呼び出される関数です。
終了処理を書くと便利。
~TestA() {  /** 終了処理 **/ }



そして

// クラスを引数に持つただの関数
void Func( TestA a ) { /** なにかしら処理を書く **/ }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// コンストラクタが呼ばれる
TestA test1;

// 関数を呼び出す。 デフォルトコピーコンストラクタが呼ばれる。
Func( test1 );


と、書いた場合、test1のmpNumberはぶち壊れます。
そらもう、跡形もなく。。


なぜかというと、
Func関数に入るときコピーコンストラクタが発生して a が生成されるわけです。この時 test1 の変数 mpNumber アドレスa にコピーされます。
で、Funcが終了した時、は消えるのでデストラクタが呼ばれます。

はい、a mpNumberは抹殺されますね。


test1
a同じmpNumberアドレスを保持していたので、

test1
mpNumberも抹殺されます。
ぐわあぁああああああああああああ!!!!!


これは大変危険きわまりない行為ですね。
デバッグ中にイラッ☆とするバグ、メモリ参照エラーが発生します。
わかりにくいバグの1つですね。


なので、コピーするならば

class TestA
{
private:
    /** メンバ変数 **/
    int* mpNumber;

public:

   // コンストラクタ
   TestA()
   {
        mpNumber = new int;
        mpNumber = 1;
   }
   // コピーコンストラクタ
   TestA( const TestA& cpy )
   {
      mpNumber = new int;         // メモリを取り
        *mpNumber = *( cpy.mpNumber );  // 値を代入( 安全 )
   }

   // デストラクタ
   ~TestA()
   {
// 削除されるのは自分だけが保持しているアドレス
  delete mpNumber;
   }
};



と書くと、うまくいきます。
コピーコンストラクタを自分で記述する判断はポインタの有無で決めてもいいと思います。

他にもスマートポインタという方法もあります。
これもいつか書いてみたい。




最後に

class TestA
{
private:
    /** メンバ変数 **/
    int mID;
    int* mpNumber;

public:
    // コンストラクタ
    TestA( int id, int* pNumber )
    {
mID = id;
   mpNumber = pNumber;  // アドレスの代入
    }
};


この書き方を使用するとなぜ少し無駄になるかというと、
例えば、

class TestA
{
private:
    /** メンバ変数 **/
    int mID;
    TestB mTestB;

public:
    // コンストラクタ
    TestA( int id, TestB& testB )
    {
mID = id;
   mTestB = testB;  // TestBのコピーコンストラクタが呼び出される
    }
};


と新たにTestBを記述して、TestAがそれを保持していた場合を考えます。
このコンストラクタの流れは、

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
① TestAが呼びだされる。
② mTestBのコンストラクタが呼ばれる。
③ TestAのコンストラクタが呼ばれる。
④ mIDの代入。
⑤ mTestBのコピーコンストラクタが呼ばれる。
⑥ 終了。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

明らかに ② が要らない。
最初からコピーコンストラクタで呼び出したらいい。

後、メンバ変数の順番に上から記述すると多少高速になるみたいです。

ここは個人の好みだと思いますが初期化処理の記述を少し別にすることで、コードの明瞭性も上がるのではないでしょうか。


以上ですm(_ _)m

後は explicit というコンストラクタをちょっと特殊にする記述もあります。
また今度書いてみたいどえす。



単純な凡ミスに数十分も躓いてるとか、そらないわなぁ~(´Д`)
以後気をつけたい。