クラスを定義する - C++ プログラミング
PROGRAM
C++ でクラスを定義する
C++ ではヘッダーファイルと実装ファイルの 2 つのファイルを使ってクラスを定義して行くことになります。
そのうちのヘッダーファイルというのは、クラスの名前や、そのクラスがどのような属性(内部変数)やメソッドを持っているか、それらにどのレベルでアクセスできるかといった、プロトタイプを定義するためのファイルです。
実際の実装は、通常はここでは記載せず、実装ファイル内で記載します。
C++ では、ヘッダーファイルの拡張子は "*.h" に、実装ファイルの拡張子は "*.cpp" にするのが一般的です。
ヘッダーファイルでのクラス定義
ヘッダーファイル内での C++ クラスの宣言について見て行きます。
たとえば "MyClass.h" というヘッダーファイルを次のように用意してみます。
// このヘッダーファイルを重複して #include されたときに、定義が重複エラーになるのを避けるようにします。
#pragma once
// クラス CMyClass を定義しています。クラス名は "C" で始めるのが通例です。
class CMyClass
{
private:
// 属性(内部変数)は通常は private として定義します。変数名は "m_" で始めるのが通例です。
const char* m_name;
const char* m_description;
char* m_string;
public:
// クラス構築時に呼ばれるコンストラクタは、戻り値を付けずに "クラス名" で宣言します。
CMyClass();
CMyClass(const CMyClass& myClass);
CMyClass(const char* name, const char* description);
// クラス破棄時に呼ばれるデストラクタは、戻り値を付けずに "~クラス名"で宣言します。virtual は、このメソッドがオーバーライド可能であることを示しています。
virtual ~CMyClass();
// const 指定の変数に格納した場合にも使えるようにしたいメソッドは const 指定します。
char* toString(char* buffer, size_t bufferLength) const;
// 属性には、通常はメソッドを通して読み書きするようにします。
const char* name() const;
void setName(const char* name);
const char* description() const;
void setDescription(const char* description);
// C++ では演算子のオーバーロードが簡単にできます。
CMyClass& operator=(const CMyClass& myClass);
};
ヘッダーファイルは基本的に、実体を伴わないプロトタイプ宣言だけで構成します。
いくつか要所を含めながら記載しましたが、C++ でのクラス宣言の要所としては class キーワードで始めて、C 構造体と同じように { } で括った中に項目を書き、最後にセミコロンで締めくくる形になります。
重複定義の防止
C++ のクラス定義でさりげなく大事になるのが、次のような二重インクルードを防止するコードです。
#pragma once
C++ では、同じヘッダーファイルが複数回インクルードされたとき、その都度そのヘッダーファイルの内容を処理するようになっています。
そのため、何も対策を打っていないと、もしどこかで同じヘッダーファイルを複数回インクルードしたときに、そこに記載されているクラスの定義が 2 重に定義されてコンパイルエラーになってしまいます。
それを避けるために、このファイルは 1 度だけしか読み込まないで欲しいを示す #pragma once を記載しています。
コンパイラによっては #pragma once が使えない場合ももしかするとあるかもしれないので、その場合には #define を使って二重インクルードを回避することになります。
それについては 同じクラスの再定義を防止する の方に記しておきます。
アクセス指定子
途中、好きなタイミングで "アクセス指定子" を記載することができます。
アクセス指定子には "private:", "protected:", "public:" というのがあって、それぞれ次のような意味があります。
private: | 自分自身から、自分とまったく同じクラスのインスタンス内から、または friend 指定された関数やクラスからだけアクセスできます。 |
---|---|
protected: | 自分自身から、自分とまったく同じクラスのインスタンス内から、または自分を継承している派生クラスからだけアクセスできます。 |
public: | どこからでもアクセスできます。 |
クラスに定義された属性やメソッドは、直前に指定したアクセス指定子の範囲で利用できます。
コンストラクタの定義
このクラスのインスタンスを生成すると自動で呼び出されるコンストラクタは、戻り値を書かず、メソッド名をクラス名と同じにして宣言します。
CMyClass();
CMyClass(const CMyClass& myClass);
CMyClass(const char* name, const char* description);
ここでは 3 パターンのコンストラクタを定義していますが、このように、引数が異なるコンストラクタをいくつでも自由にオーバーロードすることができます。
この内 2 番目の、自分自身のクラス参照を引数に取るコンストラクタは、コピーコンストラクタと言われます。
コピーコンストラクタについては クラスインスタンスをコピーする の方で整理しておきます。
また、C++11 に対応しているコンパイラの場合は、ムーブコンストラクタの実装もおすすめです。
CMyClass(CMyClass&& myClass);
このようなムーブコンストラクタを定義して実装しておくと、関数の戻り値として得られたこのクラスを変数に代入するときに、内部的に効率的に戻り値を取得することができるようになります。
ムーブコンストラクタの詳細については 右辺値参照とムーブコンストラクタの使い方 の方に記しておきます。
デストラクタ
クラスのインスタンスが解放されたときには、デストラクタが自動で呼び出されます。
virtual ~CMyClass();
デストラクタは、クラス名の前に "~" を書き、戻り値の型を書かないようにすることで宣言できます。引数は受け取れません。
自動で呼び出されるメソッドですが、アクセス指定子は必ず "public:" にしておく必要があります。
また、今回の例では "virtual" を指定しています。
この virtual というのは、このメソッドは継承先でオーバーライド可能であることを示すキーワードです。詳細については 仮想関数を定義してオーバーライド可能にする の方に整理しておきます。
読み取り専用のインスタンスで使えるメソッド
C++ クラスのインスタンスは const を指定した変数に格納すると、そのインスタンスは読み取り専用として扱われます。
このとき、通常のメソッドは呼び出せないようになり、メソッド定義の末尾に const が記載されているものだけが利用できるようになります。
char* toString(char* buffer, size_t bufferLength) const;
同じ名前と引数のメソッドでも const があるかないかで別ものなので、const の場合とそうでない場合と、それぞれ異なる戻り値を返すメソッドを定義することもできるようになっています。
このあたりについては 読取専用時に使えるメソッドを定義する に整理しておきます。
演算子のオーバーロード
C++ では、演算子をオーバーロードして、演算子の動作を変更することができます。
クラスを実装したときによくオーバーライドするのは "=" 演算で、インスタンスに同じクラス型の値が代入されたときに、適切なコピー処理を行いたいときに便利です。
これを実装しないと、属性の値が単純にシャローコピーで複製されるコードになります。
演算子が取る引数の数はあらかじめ決まっていて、型だけを自由に指定できるようになっています。
そして、通常はその演算子が定義されているクラスに対して何かを演算するときに、実装した演算が処理されます。
任意の値に自分自身を演算する処理を実現したい場合には、任意の値に演算子を定義するか、friend 指定した演算子を実装する必要があります。
実装ファイルでのクラス定義
実装ファイル内での C++ クラスの定義では、関数を定義する要領で淡々と機能を実装して行きます。
たとえば "MyClass.cpp" という実装ファイルは次のような感じになります。
要所としては、最初にクラスのプロトタイプが定義されているヘッダーをインクルードすることと、メソッドの定義のときにはメソッド名の前にクラス名をスコープ演算子 (::) を挟んで付与する必要があるところでしょうか。
// クラスのプロトタイプが宣言されたヘッダーファイルをインクルードします。
#include "MyClass.h"
// 各メソッドは、名前に "クラス名::" を付けて実装を定義します。
CMyClass::CMyClass()
{
// C++ では属性の初期化が必ず必要です。
m_name = NULL;
m_description = NULL;
m_string = NULL;
}
CMyClass::CMyClass(const CMyClass& myClass)
{
// 同じクラスであれば private 属性にもアクセスできます。
m_name = myClass.m_name;
m_description = myClass.m_description;
m_string = NULL;
}
CMyClass::CMyClass(const char* name, const char* description)
{
m_name = name;
m_description = description;
m_string = NULL;
}
CMyClass::~CMyClass()
{
// delete 演算子は NULL だった場合には何もしないので事前のチェックは不要です。
delete [] m_string;
// デストラクタなのでこれで完全に役目終了ですが、それでも NULL を代入しておいた方が安全です。
m_string = NULL;
}
const char* CMyClass::toString(char* buffer, size_t bufferLength) const
{
snprintf(buffer, bufferLength, "%s: %s", m_name, m_description);
return buffer;
}
const char* CMyClass::name() const
{
return m_name;
}
void CMyClass::setName(const char* name)
{
m_name = name;
}
const char* CMyClass::description() const
{
return m_description;
}
void CMyClass::setDescription(const char* description)
{
m_description = description;
}
クラスの実装方法とは直接関係ないコメントも含めてみましたが、C++ のクラスはこんな感じで実装して行くことになります。
[ もどる ]