C++ ハンドルのクラス化【C言語のハンドルをクラスで再構築】

C++
この記事は約18分で読めます。

こんにちは、ナナです。

C言語はオブジェクト指向言語ではありませんが、オブジェクトを疑似的に扱うための「ハンドル」という概念があります。

「ハンドル」とはオブジェクト指向におけるクラスの「カプセル化」に相当する機能になります。

今回の記事ではクラス作成の練習として、C言語で扱う「ハンドル」をC++の「クラス」に置き換えてみましょう。

C言語のハンドル構成は『C言語 ハンドルの作り方【オリジナルのハンドルを自作する方法】』の記事をベースとします。事前に読んでおきましょう。

それでは、クラス構築の具体的なプログラムイメージを掴んでいきましょう。

本記事で学習できること
  • クラスのメンバ変数として銀行口座の情報を登録する方法とは?
  • クラスのメンバ関数として「生成」「制御」「破棄」を登録する方法とは?
  • 利用者側から見た「銀行口座」クラスの使い方とは?
  • 「ハンドル」と「クラス」の違いとは?
スポンサー

「銀行口座」クラスのメンバ変数を決める

「ハンドル」って銀行口座を作ったあのプログラムのことね!C言語で一度は作ってるんだから簡単よっ!

うん?えーと、何からすればいいのかを教えなさいっ?

ナナ

まずはデータ構造を決めようね。構造体として定義した銀行口座を「クラス」として定義し直してみようね。

銀行口座情報をメンバ変数としてクラスに登録

銀行口座は次の情報を持っています。クラス名「BankAccount」として定義してみましょう。

管理項目データ型メンバ名説明
登録氏名char[64]mNameキャッシュカードを利用する人の名前を管理
暗証番号unsigned shortmPincode不正利用防止のため、16進数4桁の暗証番号を管理
預金残高unsigned longmBalance預け入れや引き出しによって変わる預金の残高を管理
#ifndef BankAccount_h
#define BankAccount_h
//---------------------------------------------------------

#define D_ACCOUNT_NAMESIZE      (64)            //  氏名サイズ

class BankAccount
{
private:
    char            mName[D_ACCOUNT_NAMESIZE];   //  登録氏名
    unsigned short  mPincode;                    //  暗証番号
    unsigned long   mBalance;                    //  預金残高
};

//---------------------------------------------------------
#endif
ナナ

メンバ変数のアクセス指定子は「private」が基本でしたね。

銀行の口座情報に外部からアクセスされては困るのです。

スポンサー

銀行口座のアカウント作成:クラスオブジェクトの生成

クラス定義に銀行の口座情報は登録したわよ!

さぁ、次は何をすればいいの?早く教えなさいっ!

ナナ

ハンドルにおいても、まずは「ハンドル生成」が必要だったね。

C++においての「オブジェクト生成」に必要な処理を実装しようね。

オブジェクトの生成を司る関数と言えば「コンストラクタ」ですね。

オブジェクトの生成関数:コンストラクタの定義

コンストラクタの定義はクラス名と同じでした。C言語で定義したハンドル生成の「creatBankAccount関数」と同じ引数構成で定義してみましょう。

項目データ型・変数名説明
ファイルBankAccount.c
クラス名BankAccount
関数名BankAccount
概要銀行口座アカウントのオブジェクトを生成する。
引数1const char * name最大64文字までの登録氏名
引数2unsigned short pincodeカードの暗証番号
戻り値コンストラクタのためなし
#ifndef BankAccount_h
#define BankAccount_h
//---------------------------------------------------------

#define D_ACCOUNT_NAMESIZE      (64)            //  氏名サイズ

class BankAccount
{
private:
    char            mName[D_ACCOUNT_NAMESIZE];   //  登録氏名
    unsigned short  mPincode;                    //  暗証番号
    unsigned long   mBalance;                    //  預金残高

public:
    BankAccount(const char * name, unsigned short pincode);
};

//---------------------------------------------------------
#endif
#include <stdio.h>
#include <string.h>

#include "BankAccount.h"

BankAccount::BankAccount(const char * name, unsigned short pincode)
{
    //  口座情報を初期化
    strcpy_s(mName, D_ACCOUNT_NAMESIZE, name);
    mPincode = pincode;
    mBalance = 0;
}
ナナ

コンストラクタは「public」なアクセス指定子に定義するのでしたね。

「登録氏名」と「暗証番号」は生成時に必須な情報のため、デフォルトコンストラクタは定義しません。

スポンサー

銀行口座のサービス提供:クラスのメンバ関数定義

銀行口座がこれで作れるようになったのね。私のお小遣いを預金させていただくわよ。

預金や引き出しはどうしたらできるの?

ナナ

預金や引き出しのサービスはまだ作ってないね。

それでは、銀行口座の基本サービスである「残高照会」「預け入れ」「引き出し」の手続きをクラスに登録してみましょう。

メンバ関数の定義:銀行口座の残高照会

ハンドルで定義した「printBalance関数」に相当する機能をクラスに定義してみましょう。

項目データ型・変数名説明
ファイルBankAccount.c
クラス名BankAccount
関数名printBalance
概要銀行口座に登録されている氏名と預金残高を表示する。
表示フォーマットは次の通りとする。
————————–
氏名:山田太郎様
残高:0円
戻り値int正常:0
異常:-1
#ifndef BankAccount_h
#define BankAccount_h
//---------------------------------------------------------

#define D_ACCOUNT_NAMESIZE      (64)            //  氏名サイズ

class BankAccount
{
private:
    char            mName[D_ACCOUNT_NAMESIZE];   //  登録氏名
    unsigned short  mPincode;                    //  暗証番号
    unsigned long   mBalance;                    //  預金残高

public:
    BankAccount(const char* name, unsigned short pincode);
    int printBalance();
};

//---------------------------------------------------------
#endif
int BankAccount::printBalance()
{
    printf("-----------------------------\n");
    printf("氏名:%s様\n", mName);
    printf("残高:%d円\n", mBalance);

    return 0;
}
ナナ

C言語のハンドル制御では、引数に「ハンドル」を指定しました。クラスにおいては、呼び出したオブジェクトがハンドルの代わりになるため、引数に指定する必要がありません。

これがオブジェクト指向ならではのプログラムスタイルです。

メンバ関数の定義:銀行口座への預け入れ

続いて、銀行口座へのお金を預け入れできるようにしてみましょう。

項目データ型・変数名説明
ファイルBankAccount.c
クラス名BankAccount
関数名depositMoney
概要銀行口座へのお金の預け入れを行う。
引数1unsigned long money預け入れする金額(円)
戻り値int正常:0
異常:-1
int BankAccount::depositMoney(unsigned long money)
{
    //  預入のお金を残高に加算
    mBalance += money;

    return 0;
}

ハンドルのチェックが不要となっているため、C言語の時と比べて処理がシンプルになっているのがわかりますね。

ナナ

ヘッダファイルにも「depositMoney」を登録しておきましょう。

メンバ関数の定義:銀行口座からの引き出し

続いて、銀行口座からお金を引き出すことができるようにしてみましょう。

項目データ型・変数名説明
ファイルBankAccount.c
クラス名BankAccount
関数名withdrawalMoney
概要銀行口座からお金の引き出しを行う。
引数1unsigned short pincode暗証番号
引数2unsigned long reqmoney引出し希望金額(円)
戻り値int引出した金額:0以上
その他の異常:-1
暗証番号不一致:-2
残高不足:-3
int BankAccount::withdrawalMoney(unsigned short pincode, unsigned long reqmoney)
{
    //  暗証番号確認
    if (mPincode != pincode)
    {
        return -2;
    }

    //  残高確認
    if (mBalance < reqmoney)
    {
        return -3;
    }

    //  残高から減算
    mBalance -= reqmoney;

    return reqmoney;
}
ナナ

ヘッダファイルにも「withdrawalMoney」を登録しておきましょう。

スポンサー

銀行口座の解約:クラスオブジェクトの破棄

ここまで結構頑張ったわよ。やらなくちゃいけないことはまだ残ってるの?

ナナ

最後に銀行口座の解約にあたる「破棄」処理を定義しておこうね。

メンバ関数の定義:銀行口座の解約手続き

ハンドルの時は暗証番号をチェックしましたが、C++のデストラクタには引数が指定できません。そのため、本人確認なしで破棄を行います。

項目データ型・変数名説明
ファイルBankAccount.c
クラス名BankAccount
関数名~BankAccount
概要銀行口座の破棄を行う。
戻り値デストラクタのためなし

今回のクラスでは、特別何か必要な処理がないため、デストラクタの定義は空としましょう。

BankAccount::~BankAccount()
{
    // 特に処理なし
}

今回のケースではデストラクタで特別必要な処理がないため、定義せずに自動定義されたデストラクタを利用しても問題ありません。

#ifndef BankAccount_h
#define BankAccount_h
//---------------------------------------------------------

#define D_ACCOUNT_NAMESIZE      (64)            //  氏名サイズ

class BankAccount
{
private:
    char            mName[D_ACCOUNT_NAMESIZE];   //  登録氏名
    unsigned short  mPincode;                    //  暗証番号
    unsigned long   mBalance;                    //  預金残高

public:
    BankAccount(const char* name, unsigned short pincode);
    ~BankAccount();
    int printBalance();
    int depositMoney(unsigned long money);
    int withdrawalMoney(unsigned short pincode, unsigned long reqmoney);
};

//---------------------------------------------------------
#endif
#include <stdio.h>
#include <string.h>
#include "BankAccount.h"

BankAccount::BankAccount(const char* name, unsigned short pincode)
{
    //  口座情報を初期化
    strcpy_s(mName, D_ACCOUNT_NAMESIZE, name);
    mPincode = pincode;
    mBalance = 0;
}

BankAccount::~BankAccount()
{

}

int BankAccount::printBalance()
{
	printf("-----------------------------\n");
	printf("氏名:%s様\n", mName);
	printf("残高:%d円\n", mBalance);

	return 0;
}

int BankAccount::depositMoney(unsigned long money)
{
    //  預入のお金を残高に加算
    mBalance += money;

    return 0;
}

int BankAccount::withdrawalMoney(unsigned short pincode, unsigned long reqmoney)
{
    //  暗証番号確認
    if (mPincode != pincode)
    {
        return -2;
    }

    //  残高確認
    if (mBalance < reqmoney)
    {
        return -3;
    }

    //  残高から減算
    mBalance -= reqmoney;

    return reqmoney;
}
スポンサー

クラスオブジェクト編:銀行口座を使って預金の出し入れをしてみる

もうクタクタよっ!そろそろ、銀行口座のサービスを受けられるんでしょうねっ!私は利用者なのよ!

ナナ

ようやくサービスの準備が整ったね。じゃあ、最後にお待ちかねの利用者側のプログラムを作ってみようね。

ここまででクラス「BankAccount」が作成できました。それでは、クラスオブジェクトを作ってサービスを利用してみましょう。

銀行口座を利用するmain側のプログラム

「野口英世」「福沢諭吉」に銀行口座を利用していただきましょう。次のようになります。

#include "BankAccount.h"

int main()
{
    //------------------------------------------------
    //  スタックメモリのクラスオブジェクトを生成
    BankAccount hideyo("野口英世", 0x1876);

    hideyo.printBalance();
    hideyo.depositMoney(5000);              //  5,000円預け入れ
    hideyo.printBalance();
    hideyo.withdrawalMoney(0x1876, 1000);   //  1,000円引き出し
    hideyo.printBalance();

    //------------------------------------------------
    //  動的メモリのクラスオブジェクトを生成
    BankAccount * yukichi = new BankAccount("福沢諭吉", 0x1835);

    yukichi->printBalance();
    yukichi->depositMoney(100000);          //  10,0000円預け入れ
    yukichi->printBalance();
    yukichi->withdrawalMoney(0x1835, 30000);//   30,000円引き出し
    yukichi->printBalance();

    delete yukichi;     //  オブジェクトの解放

    return 0;
}
-----------------------------
氏名:野口英世様
残高:0円
-----------------------------
氏名:野口英世様
残高:5000円
-----------------------------
氏名:野口英世様
残高:4000円
-----------------------------
氏名:福沢諭吉様
残高:0円
-----------------------------
氏名:福沢諭吉様
残高:100000円
-----------------------------
氏名:福沢諭吉様
残高:70000円

「ハンドル」の場合は動的メモリが基本でしたが、「クラスオブジェクト」の場合はスタックメモリでもヒープメモリでも利用することができます。

皆さんも自分自身の名前で、預け入れや引き出しの処理を行ってみてください。

ナナ

C言語で利用したハンドルとそれほど大差のないプログラムができたと思いませんか?

C言語で得た知識はこのようにオブジェクト指向でも役に立つのです。

スポンサー

Q&A:「ハンドル」と「クラス」の違いに関するよくある質問

Q:ハンドル制御関数では引数にハンドルが必要なのに、クラスのメンバ関数ではオブジェクトが必要ないのはなぜ?

ちょっと、おかしいところを見つけてしまいましたわ!

ハンドル制御の関数たちは、必ず「ハンドル」を引数に持つルールでしたわよね?なぜ、クラスのメンバ関数にはそれがないのよっ!

あなたっ、説明できるのかしら?

ナナ

よく違いを考察できていますね。それはオブジェクト指向で動作するプログラムにカラクリがあるんですよ。

次のように、C言語で定義したハンドル制御関数は、引数として「H_CASHCARD型」のハンドル情報が含まれています。

int printBalance(H_CASHCARD hCard);
int depositMoney(H_CASHCARD hCard, unsigned long money);
int withdrawalMoney(H_CASHCARD hCard, unsigned short pincode, unsigned long reqmoney);

引数として「ハンドル」が渡されることで、情報を操作する対象となるハンドルが特定されるというのがハンドル制御関数が動く仕組みです。

しかし、次のようにC++のクラスで定義されたメンバ関数には、同じ役割の引数が存在しません。

int printBalance();
int depositMoney(unsigned long money);
int withdrawalMoney(unsigned short pincode, unsigned long reqmoney);

クラスのメンバ関数には、実は「thisポインタ」と呼ばれるオブジェクトを示す特別なポインタがこっそりメンバ関数に追加されています。

それこそが、C言語のハンドルの引数に相当するものになります。

つまり、プログラムの記述上では書かれていないだけで、実は同じ仕組みで動作しているということなのです。

ナナ

「thisポインタ」に関しては次の記事で解説しますので、そこで学びましょう。

Q:デストラクタの処理にメモリ解放処理がないのはなぜ?

おかしいわよ!C言語のハンドル破棄である「destroyBankAccount関数」では、free関数によるメモリ解放処理があったわよね。

今回の「BankAccount」クラスのデストラクタには、なぜdeleteによるメモリ解放処理が存在しないのよっ。あなた、しっかりと説明なさいっ!

ナナ

この答えは「誰がそのメモリを管理しているのか?」を正しく理解できていればわかりますよ。

C言語のハンドルでは「destroyBankAccount関数」にて動的メモリの解放を行いました。

int destroyBankAccount(H_CASHCARD hCard, unsigned short pincode)
{
    ・・・省略・・・

    free(pAccount);

    return 0;
}

C++ではなぜ必要ないのでしょうか?

それは

「ハンドル」と「クラスオブジェクト」のメモリ領域を誰が管理しているか?

が異なるからです。

ハンドルの場合

ハンドルでは、銀行側により動的メモリが管理される構成となっています。

クラスオブジェクトの場合

このように「ハンドル」や「クラスオブジェクト」のメモリ解放は、確保した方が実施するのが基本となります。

「クラスオブジェクト」の場合は、利用者側がその義務を持っているということになります。

ナナ

クラスのメンバ変数の中に動的メモリなどの資源を持っている場合は、デストラクタで資源解放の処理を実装しますよ。

「クラスオブジェクト変数のメモリを誰が管理しているか?」ですから、勘違いしないでくださいね。