[Work/Class/C++11の場合/CPPBasic]

C++11の場合 - クラスの定義とインスタンス化,ヘッダファイルへの分割

クラスの定義

コンストラクタ

Javaと同じ.インスタンス化する時に呼ばれる.引数あり,引数なしなど複数存在させ,オーバーロードさせることもできる.

デストラクタとdeleteキーワード

ガベージコレクションを持つJavaにはないのが「デストラクタ」である.配列の動的確保malloc()の項で説明したが,mallocで動的確保した配列は必ずfreeしなければならない.メンバ変数として動的確保する配列を持つ場合,freeをどこで行うかが問題となる.

そこで,newと同じ扱いであるdeleteキーワードを,インスタンスを保持しているポインタ変数に対して実行することで,そのインスタンスのデストラクタが呼ばれることが保証されている.つまりデストラクタ内部でfreeをすることで,クラスのメンバ変数として動的確保する配列を使うことができるようになる.

先に説明した通り,C言語ではローカル変数として配列を宣言すると配列の要素数に定数を入れなければならないため,変数により配列の要素数を決定することができない.mallocで配列を動的確保すると,配列の要素数に変数を使うことができる.インスタンスごとに保持するデータ量(配列の要素数)を変えたい場合にはmallocが必要になるため,デストラクタが必要になる.

デストラクタの指定は,コンストラクタと同じくクラス名がついた関数だが,頭に「~」がつく.

アクセス制限: protected, public, private

Javaでは,メンバ関数やメンバ変数に自動的にprotected修飾子がついた.これは「パッケージ内部(Processingでは同じプロジェクト)やそのクラスを継承したクラスからはアクセスできるが,それ以外からはアクセスできない」というものである.

またJavaでprivateやpublicをつける時には,変数ごとに指定していた.

C++はJava同じくprivateとprotected, publicを備えているが,指定の方法はJavaとは異なり,private:の後に記述したものはprivate,さらにその後のpublic:の後に指定したものはpublic,という感じで,ブロックごとに指定する.

C++にはパッケージはないので,protectedの意味は基本的に「クラス内の関数からアクセスできる」「継承した子クラス,孫クラス内の関数からも参照できる」となる.(C++は「継承の仕方」としてpublic, protected, privateが選べるため,実際はもう少し複雑である)

したがって,二重三重の継承を前提としたクラスであれば,privateではなく,Javaと同じようにprotectedを使った方が良い.

(C++にはfriendという概念もあるが,基本的にイレギュラーなので,演算子のオーバーロー以外では使わないのが一般的)

private: 
    // ここに書かれたものはprivate
    // 一般的にはインスタンスメンバ変数を記述する
    int windowWidth, windowHeight;
    int xPos, yPos;
    int xDirection, yDirection;
public:
    // ここに書かれたものはpublic
    // 一般的にはインスタンスメンバ関数を記述する
    BounceBall(){ // デフォルト(引数なし)コンストラクタ
        // コンストラクタの処理内容
    }
    BounceBall(int width, int height){ // 引数付きコンストラクタ
        // コンストラクタの処理内容
    }

    ~BounceBall(){ // デストラクタ
        // デストラクタの処理内容
    }

    void updateParameters(){ // インスタンスメンバ関数
        // 処理内容
    }

という形になる.

インスタンス化

プリミティブ型と同じローカル変数として

//クラス名 変数名(コンストラクタの引数);
BounceBall aBall(640, 480);

ローカル変数として静的確保した配列の宣言と同時に,デフォルトコンストラクタを使って,複数個を同時にインスタンス化

//クラス名 配列変数名[配列の要素数];
BounceBall ballArray[10];

newしてポインタで受け取る

//クラス名* ポインタ変数の名前 = new クラス名(コンストラクタの引数);
BounceBall* aBallPtr = new BounceBall(640, 480);

ポインタで保持している場合,インスタンス内部にアクセスする時は「インスタンスのポインタ.メンバ関数()」ではなく「->」を用いる.

また使い終わったら,インスタンスを保持するポインタ変数に対してdeleteが必要になる.

ローカル変数として静的確保したポインタの配列にnewする

//クラス名* ポインタ配列の名前[要素数];
//for(int i=0; i<要素数; i++)
//   ポインタ配列[i] = new クラス名(コンストラクタの引数);
BounceBall* ballArray[10];
for(int i=0; i<10; i++)
   ballArray[i] = new BounceBall(640, 480);

配列はローカル変数なのでfreeはいらないが,インスタンスのdeleteは必要

動的確保したポインタの配列にnewする

//クラス名** ポインタ配列の名前 = (クラス名**)malloc(sizeof(クラス名*) * 要素数);
//for(int i=0; i<要素数; i++)
//    ポインタ配列[i] = new クラス名(コンストラクタの引数);
BounceBall** ballPtrArray = 
    (BounceBall**)malloc(sizeof(BounceBall*) * numOfBall);
for(int i=0; i<numOfBall; i++)
    ballPtrArray = new BounceBall(640, 480);

インスタンスのdeleteと配列のfree両方が必要

コード例

// BounceBall.cpp
#include <stdio.h>
#include <stdlib.h>

class BounceBall{
  // C++のクラスでは,クラス内の関数や変数に対してそれぞれ
  // publicやprivateをつけるのではなく
  // まとめて,これ以下はpublic, これ以下はprivate,という感じで指定する.
  
  // JavaのデフォルトであるprotectedはC++にはないため,
  // 明示的にpublicかprivateをつける必要がある
public:
  BounceBall(){
    // デフォルトコンストラクタ
    // 引数を取らない場合呼ばれる.必ず作ること.
    
    // 超重要!!!
    // C++では,コンストラクタ内でthisを使ってはいけない,という制約がある
    
    windowWidth = 640;
    windowHeight = 480;
    xPos = (int)((float)rand() / (float)RAND_MAX * windowWidth);
    yPos = (int)((float)rand() / (float)RAND_MAX * windowHeight);
    if((float)rand() / (float)RAND_MAX > 0.5){
      xDirection = 1;
    }
    else{
      xDirection = -1;
    }

    if((float)rand() / (float)RAND_MAX > 0.5){
      yDirection = 1;
    }
    else{
      yDirection = -1;
    }

    printf("xPos, yPos : %d, %d\n", xPos, yPos);
  }
  
  BounceBall(int width, int height){
    // 引数付きのコンストラクタ
    windowWidth = width;
    windowHeight = height;
    xPos = (int)((float)rand() / (float)RAND_MAX * windowWidth);
    yPos = (int)((float)rand() / (float)RAND_MAX * windowHeight);

    if((float)rand() / (float)RAND_MAX > 0.5){
      xDirection = 1;
    }
    else{
      xDirection = -1;
    }

    if((float)rand() / (float)RAND_MAX > 0.5){
      yDirection = 1;
    }
    else{
      yDirection = -1;
    }

    printf("xPos, yPos : %d, %d\n", xPos, yPos);
  }
  
  ~BounceBall(){
    // デストラクタ
    // コンストラクタの反対で,deleteされる時に呼ばれる
    // もしクラス内変数として,配列等をmallocしていたり,他のクラスをnewしている時は
    // このデストラクタ内でfreeやdelteを行う
  }
  
  // インスタンス関数
  void updateParameters(){
    xPos += xDirection * 10;
    yPos += yDirection * 10;

    if(xPos > windowWidth){
      xDirection = -1;
    }
    else if(xPos < 0){
      xDirection = 1;
    }
    if(yPos > windowHeight){
      yDirection = -1;
    }
    else if(yPos < 0){
      yDirection = 1;
    }

    printf("xPos, yPos : %d, %d\n", xPos, yPos);
  }
  
private:
  int windowWidth, windowHeight;
  int xPos, yPos;
  int xDirection, yDirection;
};


int main(){
  printf("--------------------------------------------------\n");

  // ローカル変数の実体として引数付きコンストラクタを呼び出す
  printf("Instantiate\n");
  BounceBall ball(640, 480);
  printf("Update Parameters\n");
  ball.updateParameters();
  // インスタンスはローカル変数の実体なので,関数の外に出ると勝手に消える

  printf("--------------------------------------------------\n");

  // ローカル変数の実体として呼び出して,静的確保した配列に入れる
  // ポインタを使わない場合,デフォルトコンストラクタを使って,
  // 一気に必要な数をインスタンス化することができることができる
  printf("Instantiate\n");
  BounceBall staticArray[10]; //10個のインスタンスができて配列に入っている状態になる

  printf("Update Parameters\n");
  for(int i=0; i<10; i++){
    staticArray[i].updateParameters();
  }

  // 配列もローカル変数の実体なので関数の外に出れば勝手に消える.
  // インスタンスをdeleteする必要もないし,配列のfreeも必要ない


  printf("--------------------------------------------------\n");

  // newキーワードを使って引数付きコンストラクタを呼び出しインスタンス化してポインタに入れる
  printf("Instantiate\n");
  BounceBall* bounceBall = new BounceBall(640, 480);
  // 「ポインタ」で保持している場合は,「.」ではなく「->」で
  // インスタンス内部にアクセスする
  printf("Update Parameters\n");
  bounceBall->updateParameters();
  // deleteを忘れない
  delete(bounceBall);

  printf("--------------------------------------------------\n");

  // インスタンスを保持するポインタの配列を静的確保する
  BounceBall* pointerArray[10];
  // 配列の各要素にBounceBallのインスタンスをnewして入れる
  printf("Instantiate\n");
  for(int i=0; i<10; i++){
    printf("index: %d -> ", i);
    pointerArray[i] = new BounceBall(640, 480);
  }

  // インスタンス内部にアクセスする
  printf("Update Parameters\n");
  for(int i=0; i<10; i++){
    printf("index: %d -> ", i);
    pointerArray[i]->updateParameters();
  }

  // deleteする
  for(int i=0; i<10; i++){
    delete(pointerArray[i]);
  }

  // 配列そのものは静的確保なのでdeleteする必要はない

  printf("--------------------------------------------------\n");

  // インスタンスを保持する配列を動的確保する
  // 「BounceBallインスタンスを保持する」のはポインタなので,
  // sizeofの中身はBounceBallクラスのポインタになり,
  // 配列全体はポインタのポインタになる
  int numOfBall = 20;
  BounceBall** ballPointerArray = 
    (BounceBall**)malloc(sizeof(BounceBall*) * numOfBall);

  // 配列に,BounceBallのインスタンスをnewして入れる
  printf("Instantiate\n");
  for(int i=0; i<numOfBall; i++){
    printf("index: %d -> ", i);
    ballPointerArray[i] = new BounceBall(640, 480);
  }

  printf("Update Parameters\n");
  // インスタンス関数を呼び出す
  for(int i=0; i<numOfBall; i++){
    printf("index: %d -> ", i);
    ballPointerArray[i]->updateParameters();
  }

  // deleteする
  for(int i=0; i<numOfBall; i++){
    delete(ballPointerArray[i]);
  }

  // 配列をfreeする
  free(ballPointerArray);
    

}

ヘッダファイルへ分割

一般的にC++のクラス定義は,クラス定義本体,すなわちクラスに含まれる関数と変数をヘッダファイルhppに,実際の関数の処理内容はソースファイルcppに記述する,という分割方式が取られる.

関数の処理記述をする時は,クラス名::関数名という形で,コロン二つで区切って定義する.

分割すると以下のようになる.(main関数内部は省略)

ヘッダファイル側にある

#ifndef ヘッダファイルを示すユニークな文字列
#define ヘッダファイルを示すユニークな文字列
// ...クラス定義
#endif

は「もし今までこのユニークな文字列が定義されていなければ,定義して以下の定義を実行する」という意味である.

「if not define ... endif」は「もし...が定義されていなければendifまでを実行」となる.この時この処理の中で文字列を定義することで,複数回このヘッダファイルが読まれても一度しか実行されないことになる.

ヘッダファイルはクラスの定義が書かれているので,そのクラスを利用する複数のソースファイルで読み込まれることになる.この際に定義を複数回実行しないようにするための工夫である.(C言語,C++言語のコンパイラはスマートではないので,このような工夫が必要になる)

// BounceBall_Devided.hpp
#ifndef BOUNCEBALL_DEVIDED_HPP
#define BOUNCEBALL_DEVIDED_HPP

#include <stdio.h>
#include <stdlib.h>

class BounceBall{
public:
  // コンストラクタとデストラクタ
  BounceBall();
  BounceBall(int width, int height);  
  ~BounceBall();

  // インスタンス関数
  void updateParameters();
  
private:
  // privateなインスタンス変数
  int windowWidth, windowHeight;
  int xPos, yPos;
  int xDirection, yDirection;
};

#endif //BOUNCEBALL_DEVIDED_HPP
// BounceBall_Devided.cpp
#include "BounceBall_Devided.hpp"

BounceBall::BounceBall(){
  // デフォルトコンストラクタ
  windowWidth = 640;
  windowHeight = 480;
  xPos = (int)((float)rand() / (float)RAND_MAX * windowWidth);
  yPos = (int)((float)rand() / (float)RAND_MAX * windowHeight);
  if((float)rand() / (float)RAND_MAX > 0.5){
    xDirection = 1;
  }
  else{
    xDirection = -1;
  }
  
  if((float)rand() / (float)RAND_MAX > 0.5){
    yDirection = 1;
  }
  else{
    yDirection = -1;
  }
  
  printf("xPos, yPos : %d, %d\n", xPos, yPos);
}

BounceBall::BounceBall(int width, int height){
  // 引数付きのコンストラクタ
  windowWidth = width;
  windowHeight = height;
  xPos = (int)((float)rand() / (float)RAND_MAX * windowWidth);
  yPos = (int)((float)rand() / (float)RAND_MAX * windowHeight);
  
  if((float)rand() / (float)RAND_MAX > 0.5){
    xDirection = 1;
  }
  else{
    xDirection = -1;
  }
  
  if((float)rand() / (float)RAND_MAX > 0.5){
    yDirection = 1;
  }
  else{
    yDirection = -1;
  }
  
  printf("xPos, yPos : %d, %d\n", xPos, yPos);
}

BounceBall::~BounceBall(){
  // デストラクタ
}
  
// インスタンス関数
void BounceBall::updateParameters(){
  xPos += xDirection * 10;
  yPos += yDirection * 10;
  
  if(xPos > windowWidth){
    xDirection = -1;
  }
  else if(xPos < 0){
    xDirection = 1;
  }
  if(yPos > windowHeight){
    yDirection = -1;
  }
  else if(yPos < 0){
    yDirection = 1;
  }
  
  printf("xPos, yPos : %d, %d\n", xPos, yPos);
}

int main(){

  printf("--------------------------------------------------\n");
  printf("Instantiate\n");
  BounceBall* bounceBall = new BounceBall(640, 480);
  printf("Update Parameters\n");
  bounceBall->updateParameters();
  delete(bounceBall);
}