C++の基礎 - クラス
概要
staticメンバ
staticメンバ変数
クラスのメンバ変数にstatic
を付加することにより、staticメンバ変数となる。
staticメンバ変数は、クラスの定義に記述しただけでは定義したことにならないため、実体となる定義をクラス定義と同じスコープに記述する必要がある。
class CSampleClass
{
static 型名 メンバ変数名; // 宣言
};
型名 CSampleClass::メンバ変数名; // 定義
staticメンバ変数は、オブジェクトを生成せずにプログラムの開始時に既に存在しているため、自動的にゼロで初期化される。
ただし、staticメンバ変数の型が、const
な整数型、または、const
なenum
型の場合に限り、宣言と同時に初期化子を与えることができる。
class CSampleClass
{
private:
enum E { e1, e2 };
static const int ci = 100; // OK
static const E ce = e1; // OK
static const double cf = 1.0; // コンパイルエラー
static int i = 100; // コンパイルエラー
static E e = e1; // コンパイルエラー
};
コンパイルエラーになるパターンでは、定義時に初期化子を与える必要がある。
class CSampleClass
{
private:
enum E
{
e1,
e2
};
static const int ci = 100; // OK
static const E ce = e1; // OK
static const double cf;
static int i;
static E e;
};
const double CSampleClass::cf = 1.0; // OK
int CSampleClass::i = 100; // OK
CSampleClass::E CSampleClass::e = CSampleClass::e1; // OK
非staticのメンバ変数が、オブジェクト1つごとに別個に存在することに対して、staticメンバ変数は、クラスに対して1つだけ存在する。
オブジェクトが複数存在しても、各オブジェクトからただ1つの変数を共有する。
このような特徴を持つため、staticメンバ変数は、各オブジェクトが必要とする共通情報を管理することに利用できる。
以下の例では、インスタンス化したオブジェクトの総数を管理している。
// CSampleClass.h
#ifndef CSAMPLECLASS_H
#define CSAMPLECLASS_H
class MyClass
{
public:
CSampleClass();
~CSampleClass();
private:
static int msObjectCount; // staticメンバ変数の宣言
};
#endif
// CSampleClass.cpp
#include "CSampleClass.h"
// staticメンバ変数の実体
int MyClass::msObjectCount;
CSampleClass::CSampleClass()
{
msObjectCount++;
}
CSampleClass::~CSampleClass()
{
msObjectCount--;
}
クラスの外部から、staticメンバ変数にアクセスする場合は、クラス名::staticメンバ変数名
という形で、スコープ解決演算子を使用して記述する。
また、一般的ではないが、非staticなメンバ変数と同様に、<オブジェクト名>.<staticメンバ変数名>
のようなアクセスも可能である。
一般的には、staticメンバ変数は非公開である方がよいが、const static
を付加している場合は、公開しても問題ない。
クラスの定義内やメンバ関数の定義内から、自身のクラスのstaticメンバ変数へアクセスする場合は、staticメンバ変数名のみを使用して記述することができる。
staticメンバ関数
クラスのメンバ関数にもstatic
指定子を付加することができる。(staticメンバ関数)
class <クラス名>
{
static <戻り値の型> <メンバ関数名>(<引数>);
};
<戻り値の型> <クラス名>::<メンバ関数名>(<引数>)
{
// ...略
}
staticメンバ関数にconst
を付加することはできない。
また、staticメンバ関数をインライン関数にすることも可能である。
staticメンバ関数のオーバーロードも可能であるが、staticメンバ関数と非staticメンバ関数との間において、同じ名前を使用することはできない。
staticメンバ変数と同様に、staticメンバ関数もクラスに属するため、staticメンバ関数はオブジェクトを生成せずに呼び出すことができる。
<クラス名>::<メンバ関数名>(<引数>)
オブジェクト(実体)から呼び出さないため、staticメンバ関数内では、this
ポインタを使用することはできない。
また、一般的ではないが、<オブジェクト名>.<staticメンバ関数名>(<引数>)
の形で呼び出すことも可能である。
非staticなメンバ関数から、staticメンバ関数を呼び出すことはできるが、staticメンバ関数から、非staticのメンバ変数にアクセスできない。
public
なstaticメンバ関数は、通常の関数と同様に扱うことができるが、
クラスに含まれていることにより、スコープを限定できること、staticメンバ変数という専用のデータの置き場が使用できることが特徴である。
#ifndef CSAMPLECLASS_H
#define CSAMPLECLASS_H
class CSampleClass
{
public:
CSampleClass()
{
msObjectCount++;
}
~CSampleClass()
{
msObjectCount--;
}
static int GetObjectCount()
{
return msObjectCount;
}
private:
static int msObjectCount; // staticメンバ変数の宣言
};
// staticメンバ変数の実体
int CSampleClass::msObjectCount = 0;
#endif
// main.cpp
#include <iostream>
#include "CSampleClass.h"
int main()
{
std::cout << CSampleClass::GetObjectCount() << std::endl; // msObjectCount : 0
CSampleClass c1;
CSampleClass *c2 = new CSampleClass();
std::cout << CSampleClass::GetObjectCount() << std::endl; // msObjectCount : 2
delete c2;
std::cout << CSampleClass::GetObjectCount() << std::endl; // msObjectCount : 1
}
staticオブジェクト
staticクラスは、自動的にゼロで初期化された後、staticクラスが宣言された箇所が初めて実行される時にコンストラクタが呼び出される。
staticで宣言したクラスの場合、任意の関数が終了してもデストラクタは呼び出されない。
staticクラスのデストラクタは、ソフトウェア終了直前に呼び出される。
void func()
{
static CSampleClass clsSample;
}
// オブジェクトclsSampleは削除されないため、CSampleClassクラスのデストラクタは呼び出されない
staticクラス
C++ 03以前
全てのメンバがstatic
メンバであるクラスを、staticクラス(静的クラス)と呼ぶ。
staticメンバはオブジェクトを生成せずに使用できるため、staticクラスはインスタンス化の必要性がないことが特徴である。
そのため、staticクラスを記述する場合、インスタンス化を禁止するように設計する。
C++でインスタンス化を禁止するには、コンストラクタをprivate
にする。
なお、コンストラクタが呼び出される可能性は無いため、実装を記述するべきではない。
class CSampleClass
{
private:
CSampleClass(); // 唯一のコンストラクタがprivateの場合は、インスタンス化できない
};
問題点として、staticクラスのメンバ関数内において、コンストラクタを使用することである。
これは、クラス内のメンバへのアクセスとなるため、private
であってもアクセス可能となり、コンパイルエラーにならない。
これを、コンパイルエラーとする場合、コンストラクタの実装を記述しないことにより、リンクエラーを起こす方法を採ればよい。
(コンパイルは通るが、関数の実装が無いというリンクエラーが起きる)
void CSampleClass::func()
{
CSampleClass *c = new CSampleClass();
}
staticクラスの価値は、任意の機能群を1つのスコープにまとめて記述できることである。
1つのヘッダファイルにstaticクラスの機能群をまとめて、<クラス名>::<メンバ>
のようにスコープを使用してアクセスすることができる。
例えば、ファイルをコピーまたは削除する機能は、それぞれ1つの関数で完結できるため、非staticクラスではメンバ変数を持つ必要はない。
以下の例では、staticクラスを定義して、ファイルをコピーまたは削除する機能を記述している。
class FileSystem
{
public:
static void Copy(const char* src, const char* dest);
static void Delete(const char* path);
private:
FileSystem();
};
ただし、スコープを限定するという目的の場合、名前空間を使用する方がよい。
この場合、コンストラクタをprivate
にする処理も不要である。
namespace FileSystem
{
void Copy(const char* src, const char* dest);
void Delete(const char* path);
}
FileSystem::Copy("sample.txt", "sample_cp.txt");
FileSystem::Delete("sample.txt");
C++ 11以降
C++ 11以降では、デフォルトコンストラクタとデストラクタにdelete
キーワードを指定することにより、実体化を禁止することができる。
メンバにdelete
キーワードを指定する場合は、public
に記述することがセオリーである。
final
は、継承をさせないことを明示的に宣言、または、派生クラスでの仮想関数のオーバーライドをさせないことを明示的に宣言するキーワードである。
// C++ 11以降
class CSampleClass final
{
public:
CSampleClass() = delete;
~CSampleClass() = delete;
public:
static int Add(int x, int y)
{
return x + y;
}
};
Visual C++では、abstract
キーワードが使用できるため、abstract sealed
またはabstract final
を指定することにより、static
クラスを記述することができる。
ただし、その場合は、他のプラットフォームではそのまま移植できないことに注意が必要となる。
また、Visual C++では、コンパイルオプションに/permissive-
を付加することにより、独自拡張を無効化することもできる。
メンバ関数テンプレート (メンバテンプレート)
メンバ関数テンプレートとは
クラスのメンバ関数をテンプレート化することができる。
メンバ関数テンプレートには、テンプレートクラスと非テンプレートクラスがある。
非テンプレートクラスのメンバ関数テンプレート
以下の例では、クラスのメンバ関数テンプレートは、テンプレート実引数に指定した型へstatic_cast
で変換して値を返している。
型変換を実現するために、メンバ関数テンプレートを使用することは多い。
#include <iostream>
class CSampleClass
{
private:
double mValue;
public:
explicit CSampleClass(double value) : mValue(value)
{
}
template <typename U>
inline const U GetValue() const
{
return static_cast<U>(mValue);
}
};
int main()
{
CSampleClass cls(10.5);
std::cout << cls.GetValue<int>() << std::endl; // 10
std::cout << cls.GetValue<double>() << std::endl; // 10.5
}
なお、メンバ関数テンプレートの実装をクラス外部に記述する場合は、以下のようになる。
template <typename U>
の箇所が必要な理由は、非クラステンプレートのため、<クラス名><T>::
ではなく<クラス名>::
と記述する必要があるからである。
template <typename U>
const U CSampleClass::GetValue() const
{
return static_cast<U>(mValue);
}
特定の型のみをそのまま返す場合は、非テンプレートのメンバ関数を定義することもできる。
#include <iostream>
class CSampleClass
{
private:
double mValue;
public:
explicit CSampleClass(double value) : mValue(value)
{
}
inline double GetValue() const
{
return mValue;
}
template <typename U>
inline const U GetValue() const
{
return static_cast<U>(mValue);
}
};
int main()
{
CSampleClass cls(10.5);
std::cout << cls.GetValue<int>() << std::endl; // 10
std::cout << cls.GetValue() << std::endl; // 10.5
}
テンプレートクラスのメンバ関数テンプレート
以下の例では、テンプレートクラス内でメンバ関数テンプレートを使用している。
#include <iostream>
template <typename T>
class CSampleClass
{
public:
explicit CSampleClass(const T& value) : mValue(value)
{}
inline const T GetValue() const
{
return mValue;
}
template <typename U>
inline const U GetValue() const
{
return static_cast<U>(GetValue());
}
private:
T mValue;
};
int main()
{
CSampleClass<double> cls(10.5);
std::cout << cls.GetValue<int>() << std::endl; // 10
std::cout << cls.GetValue() << std::endl; // 10.5
}
ただし、テンプレートクラス外部にメンバ関数テンプレートを定義する場合、
テンプレートクラスのテンプレート仮引数とメンバ関数テンプレートのテンプレート仮引数の両方を記述する必要がある。
template <typename T, typename U>
const U CSampleClass<T>::GetValue() const
{
return static_cast<U>(GetValue());
}
デフォルトテンプレート実引数 (C++ 11以降)
C++ 11以降では、メンバ関数テンプレートのテンプレート仮引数に、デフォルト引数を与えることができる。
#include <iostream>
class CSampleClass
{
public:
explicit CSampleClass(double value) : mValue(value)
{}
template <typename U = double>
inline const U GetValue() const
{
return static_cast<U>(mValue);
}
private:
double mValue;
};
int main()
{
CSampleClass cls(10.5);
std::cout << cls.GetValue<int>() << std::endl; // 10
std::cout << cls.GetValue() << std::endl; // 10.5
}
テンプレート変換演算子
変換演算子も、メンバ関数テンプレートにすることができる。
テンプレート変換演算子により、任意の型への型変換が定義できる。
テンプレート変換演算子は、代入や初期化時の左辺の型により、テンプレート仮引数を推測している。
#include <iostream>
class CSampleClass
{
private:
double mValue;
public:
explicit CSampleClass(double value) : mValue(value)
{}
template <typename T>
inline operator T() const
{
return static_cast<T>(mValue);
}
};
int main()
{
CSampleClass cls(123.456);
int i = fStore;
double f = fStore;
std::cout << i << std::endl; // 123
std::cout << f << std::endl; // 123.456
}
テンプレート変換演算子と、非テンプレートの変換演算子を共存させることも可能である。
その場合、一致度が高い変換演算子が優先的に使用されるため、例えば、int型に変換する場合のみ、非テンプレートの変換演算子を使用することもできる。
コンストラクタテンプレート
コンストラクタテンプレートは、テンプレートクラスにおいて、異なるテンプレート実引数を与えてインスタンス化したオブジェクトがある時、互いに暗黙的な変換を行うことにある。
例えば、テンプレートクラスがコピーコンストラクタを定義する時、コピーコンストラクタを渡す場合は、
クラス名<型>
のオブジェクトを使用して、新たなクラス名<型>
のオブジェクトを生成することができる。
ただし、テンプレートクラスのテンプレート仮引数Tに当てはめる型が異なる場合は、定義したコピーコンストラクタが使用できずに、コンパイルエラーとなる。
template <typename T>
class CSampleClass
{
public:
CSampleClass(const CSampleClass<T>& rhs);
};
int main()
{
CSampleClass<int> cls1(10);
CSampleClass<int> cls2(cls1); // OK
CSampleClass<double> cls3(cls1); // コンパイルエラー
return 0;
}
以下の例では、テンプレート仮引数の違いがある場合でも、コンストラクタテンプレートを使用して、同一の型であるように扱っている。
例から分かるように、コンストラクタテンプレートは、メンバ関数テンプレートそのものである。
メンバ変数をコピーする場合は、U型からT型への型変換が必要になる。
static_cast
で変換しているが、U型とT型の関係性がstatic_cast
で変換できない場合は、コンパイルエラーが起きる。
また、同一テンプレートクラスで型が異なるの場合、かつ、private
なメンバをアクセスする必要がある場合、相手をフレンドクラスとして指定する必要がある。
#include <iostream>
template <typename T>
class CSampleClass
{
template <typename>
friend class CSampleClass;
public:
explicit CSampleClass(const T& value) : mValue(value)
{}
// CSampleClass<U> からコピー
template <typename U>
CSampleClass(const CSampleClass<U>& rhs) : mValue(static_cast<T>(rhs.mValue))
{}
inline const T GetValue() const
{
return mValue;
}
private:
T mValue;
};
int main()
{
CSampleClass<int> cls1(10);
CSampleClass<double> cls2(cls1); // OK
std::cout << cls1.GetValue() << std::endl; // 10
std::cout << cls2.GetValue() << std::endl; // 10.0
}