C++の列挙型はメンバ関数をサポートしていません。 そこで今回は列挙型でメンバ関数を実現するための最も冴えた代替策を紹介したいと思います。
列挙型を構造体でラップする
struct Animals {
enum AnimalType { DOG, CAT } type; // ①
Animals(enum AnimalType type) : type(type) {} // ②
bool isCat() { return type == CAT; } // ③
};
// 実行例
Animals cat = Animals::CAT;
cat.isCat(); // true
Animals dog(Animals::DOG);
dog.type == Animals::CAT; // false
列挙型メンバ関数の実現で最も重要なレトリックは①と②です。③は実際のメンバ関数実装例です。
①の解説
構造体の中で列挙型を宣言しています。ここで宣言された列挙体AnimalType
とそのメンバーDOG, CAT
は構造体Animals
の名前空間に属することになるため、他の名前空間に影響することもありません。
また宣言と同時にtype
という名前で構造体のメンバ変数として宣言している点も重要です。これによりAnimals
は列挙体AnimalTypeの状態を保持し、メンバ関数からアクセスすることが出来るようになります。
②の解説
もっとも重要なレトリックです。これにより列挙体から構造体への自動変換が実現されます。暗黙のコンストラクタ呼び出しという仕組みを活用しています。
このレトリックよりAnimals cat = Animals::Cat;
やAnimals dog(Animals::Dog);
といった宣言が実現されます。
③の解説
実際にメンバー関数を実装している例です。①のレトリックのおかげでメンバ変数type
と列挙値Cat
の両者にアクセスすることが出来ます。
実践編
今回は列挙型の名前をAnimalType
としていましたが、実際にはType
やTypes
等の簡潔なものに置き換えたほうが良いでしょう。列挙のメンバ名に関してはAnimalsの名前空間があるためキャメルケースにしても問題ないでしょう(CAT
-> Cat
)。
以下は今回紹介したサンプルコードのより実践的なコードです。演算子オーバーロードにより構造体と列挙型のシームレスな比較処理を実現している点にも注目してください(dog == cat
, dog == Animals::Dog
)。
struct Animals {
/** enum連携 */
enum Type { Dog, Cat } type;
Animals(enum Type type) : type(type) {}
bool operator==(const Animals& animal) { return type == animal.type; }
/** メンバ関数 */
Type raw() { return type; }
bool isCat() { return type == Cat; }
const char* sound() {
switch (type) {
case Dog: return "わん!";
case Cat: return "にゃん!";
}
}
const char* string() {
switch (type) {
case Dog: return "Dog";
case Cat: return "Cat";
}
}
std::string toString() {
return std::string(string()) + "(" + std::to_string(raw()) + "): " + sound();
}
};
// 実行例
Animals cat = Animals::Cat;
puts(cat.sound()); // "にゃん!"
puts(cat.string()); // "Cat"
puts(cat.toString().c_str()); // "Cat(1): にゃん!"
Animals dog(Animals::Dog);
dog == cat; // false
dog == Animals::Dog; // true
if (!dog.isCat()) {
Animals::Type t = dog.raw();
if (t == Animals::Dog)
printf("%d\n", t); // 0
}