Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
30
Help us understand the problem. What is going on with this article?
@nunu-e64

文字列を対応する列挙定数(enum)に変換する【C++】

More than 5 years have passed since last update.

文字列をenumで宣言された列挙型の定数に変換する【C++】

stringやchar[]型の文字列を、一致する列挙子を探して列挙定数に変換する二つの方法を紹介します。前者は簡単だが無駄が多く汚い実装方法、後者はやや複雑だがきれいな実装方法となってます。
(勉強を兼ねて作ったコードですので、ご指摘ご質問あればコメントお願いします。)

なお逆に、列挙定数を文字列として取得したいときはこちら→列挙型enumの列挙定数を文字列に変換する方法【C,C++】

単純に文字列同士の比較を行って一致する列挙子を探す方法

現在私が作成しているプログラムでは外部テキストファイルから設定を読み込んでおり、その際に各種設定値を列挙定数と同じ文字列で表現するようにしています。
外部から読み込んだ文字列は、一致する列挙子を探して列挙定数に変換しパロメータとして保存する必要があります。

これまでは私の場合、下記のように単純に文字列同士の比較を行っていました。

ColorManager.h
//色を読み込み、列挙定数として保存するクラス
class CColorManager {
public:
    bool LoadColor();
    void PrintColor();
private:
    enum color_tag {
        RED,
        BLUE,
        GREEN
    };
    color_tag color;
};
ColorManager.cpp
#include <stdio.h>
#include <string.h>
#include "ColorManager.h"

bool CColorManager::LoadColor() {
    char color_text[32];

    printf("Input Color Name.\n");

    scanf("%s", color_text);

    if (strcmp(color_text, "RED") == 0) {
        color = RED;
    } else if (strcmp(color_text, "BLUE") == 0) {
        color = BLUE;
    } else if (strcmp(color_text, "GREEN") == 0) {
        color = GREEN;
    } else {
        printf("Error: Incorrect Color Name.\n");   //不一致エラーを表示して終了
        return false;
    }

    return true;
}

void CColorManager::PrintColor() {
    printf("ColorTag = %d\n\n", color);
}
Main.cpp
#include "ColorManager.h"

int main() {
    CColorManager color_manager;

    while (true) {
        if (color_manager.LoadColor()) {
            color_manager.PrintColor(); //読込み成功したときは結果を出力
        } else {
            return 0;   //読込み失敗したときは終了
        }
    }

    return 0;
}

結果

Input Color Name.
RED
ColorTag[RED] = 0

Input Color Name.
GREEN
ColorTag[GREEN] = 2

Input Color Name.
BLUE
ColorTag[BLUE] = 1

Input Color Name.
hoge
Error

標準入力から色の名前を読み込み、一致する列挙子があった場合にその値を出力するサンプルプログラムです。
目的の動作は実現できていますが、この方法は以下のような問題があります。

  • "RED"のような平文が存在し、仮にスペルミスがあったときにコンパイルエラーが出ないため該当行の実行時までバグに気づかない
  • 列挙定数(この場合は色の種類)を書き足していくたびにif文も書き足しが必要になるため、労力が大きい+人的ミスを誘発しやすい

何より単純にコードが冗長で見苦しいですよね。

マクロ+STLのmapを組み合わせて一致する列挙定数を直接取得するクラスを自動生成する方法

if文で一つ一つ一致を確かめる方法を回避するため、STL(標準ライブラリ)のmapを使います。
列挙子の文字列をキーとし対応する列挙定数を値とする連想配列を作ることでいちいち比較することなく、文字列を列挙定数に変換できるようにします。
更に、記述コード量を減らして見やすくするために、マクロを使ってenumを定義したときに対応するmapを自動生成するようにします。

EnumManager.h
#include <map>
#include <string>
#include <stdarg.h>
#include <string.h>

//enumの定義と管理用mapの自動作成をおこなうマクロ
#define ENUM(_name, ...)                                \
    struct _name {                                      \
        enum type {__VA_ARGS__, NUM};                   \
        std::map <std::string, type> converter;         \
        _name() {                                       \
            createEnumMap(converter, #__VA_ARGS__, NUM, __VA_ARGS__);   \
        }                                               \
    }_name;

//列挙子を示す文字列をキーとし列挙定数の数値を値としたmapを作成する
template <class T> void createEnumMap(std::map<std::string, T> &_map, char* _list, int _num, ...) {
    char* listCopy = new char[255];
    char* tmpKey;
    strcpy(listCopy, _list);

    va_list args;
    va_start(args, _num);

    if ((tmpKey = strtok(listCopy, ", ")) != NULL) _map[tmpKey] = static_cast<T> (va_arg(args, int));
    for (int i = 1; i < _num; i++) {
        if ((tmpKey = strtok(NULL, ", ")) != NULL) _map[tmpKey] = static_cast<T> (va_arg(args, int));
    }
    va_end(args);
    delete[] listCopy;
}
ColorManager.h
#include "EnumManager.h"

class CColorManager {
public:
    bool LoadColor();
    void PrintColor();
private:
    ENUM(color_tag, RED, BLUE, GREEN)
    color_tag::type color;
};
ColorManager.cpp
#include <stdio.h>
#include <string.h>
#include "ColorManager.h"

bool CColorManager::LoadColor() {
    char color_text[32];

    printf("Input Color Name.\n");
    scanf("%s", color_text);

    if (color_tag.converter.find(color_text) != color_tag.converter.end()) {
        color = color_tag.converter[color_text];
    } else {
        printf("Error: Incorrect Color Name.\n");
        return false;
    }
    return true;
}

void CColorManager::PrintColor() {
    printf("ColorTag = %d\n\n", color);
}
Main.cpp
#include "ColorManager.h"

int main() {
    CColorManager color_manager;

    while (true) {
        if (color_manager.LoadColor()) {
            color_manager.PrintColor(); //読込み成功したときは結果を出力
        } else {
            return 0;   //読込み失敗したときは終了
        }
    }

    return 0;
}

結果

Input Color Name.
RED
ColorTag = 0

Input Color Name.
GREEN
ColorTag = 2

Input Color Name.
BLUE
ColorTag = 1

Input Color Name.
hoge
Error: Incorrect Color Name.

全く同じ結果を得ることが出来ています。

ENUM(color_tag, RED, BLUE, GREEN)という一行がenum宣言と同じ役割を果たし、同時に文字列変換用の連想配列も生み出しています。
EnumManagerで宣言しているマクロでは実際にはクラスを作っており、その中のメンバとして列挙型と連想配列を宣言しています。またそのクラスのコンストラクタで連想配列を自動的に初期化し、文字列と対応する列挙子とのペアを作っています。
最終的に、ColorManagerクラスから見ると、できた列挙定数は列挙型color_tag::typeに格納され、color_tag.converterが連想配列になっています。
利用の際は、color_tag.converter[文字列]とするだけで対応する定数値を手に入れることができるようになっています。

おまけ

今回は実装していませんが機会があれば下記も試してみたいです。

  • 型名.converterという書き方が冗長の為、typedefで改善する
  • 今回のマクロでは列挙定数に任意の数を指定することができない点を改善する
  • 環境が許せば.NetFrameworkのEnumクラスを使ってみる

参考

参考までに、使用技術としては下記を利用しています。馴染みのないものがあればProgrammingPlacePlus様のサイトを中心にリンクを掲載しておくので参考にご覧ください。

以上。
(利用OS:Windows7, C++コンパイラ:g++とVC++で動作確認)

30
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
nunu-e64

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
30
Help us understand the problem. What is going on with this article?