Java-подобные перечисления в C ++: инициализация статических членов класса

Austin Reuter спросил: 10 мая 2018 в 04:15 в: c++

Я пытался имитировать перечисления Java. Вот что я придумал:

template<typename Enumeration_Type>
class Enumeration {
public:
    static auto get(int value) -> const Enumeration_Type& {
        const auto result = getMap().find(value);
        return *dynamic_cast<const Enumeration_Type*>(result->second);
    };    const int value;    Enumeration(const Enumeration& other) = delete;
    Enumeration(Enumeration&& other) noexcept = delete;
    auto operator=(const Enumeration& other) -> Enumeration& = delete;
    auto operator=(Enumeration&& other) noexcept -> Enumeration& = delete;    virtual operator int() const noexcept { return value; }
protected:
    explicit constexpr Enumeration(const int value)
        : value{value} { getMap().emplace(value, this); }    ~Enumeration() = default;
private:
    static auto getMap() noexcept -> std::unordered_map<int, const Enumeration<Enumeration_Type>*>& {
        static std::unordered_map<int, const Enumeration<Enumeration_Type>*> map;
        return map;
    }
};
}

Это базовый класс для перечислений, он регистрирует указатели на экземпляры производных типов в конструкторе - таким образом он позволяет статический метод get() для доступа к перечислениям по заданным значениям. И вот производный класс:

class DataType final: public Enumeration<DataType> {
public:
    static const DataType UNSIGNED_INT;
// Other types...    const std::string_view name;    using Enumeration<DataType>::get;
private:
    constexpr DataType(const int value, const std::string_view name) noexcept
        : Enumeration<DataType>{value},
          name{name} {}
};

Вслед за исходным файлом:

const DataType DataType::UNSIGNED_INT{0x1405, "UNSIGNED INT"};
// Other types...

Кажется, что работает, но я боюсь не может быть никакой гарантии, что статические члены в производном классе должны быть инициализированы и зарегистрированы через конструктор до первого вызова get() в базовом классе. Например: может ли произойти следующее: карта в базовом классе пуста при вызове get()?

1 ответ

kiloalphaindia ответил: 10 мая 2018 в 07:58

Как уже отмечалось в моем комментарии ранее, статические переменные (значения Enum) инициализируются в фазе динамической инициализации, которая перед вызовом функции main. Поэтому вы находитесь в безопасности, если вы не используете свои "перечисления" до начала основной функции. Следующее демонстрирует случай, когда это может произойти:

#include <unordered_map>
#include <iostream>
#include <tuple>class no_such_enum_value_exception {};template<typename Enumeration_Type>
class Enumeration {
    using MapType = std::unordered_map<int, const Enumeration<Enumeration_Type>*>;
public:
    static const Enumeration_Type& get(int value)
    {
        const MapType& EnumMap = getMap();
        const auto result = EnumMap.find(value);        if (result ==  EnumMap.end()) throw no_such_enum_value_exception();        return *dynamic_cast<const Enumeration_Type*>(result->second);
    };    const int value;    virtual operator int() const noexcept { return value; }protected:
    explicit constexpr Enumeration(const int value)
        : value{value} { getMap().emplace(value, this); }    ~Enumeration() = default;private:
    static MapType& getMap() noexcept {
        static MapType map;
        return map;
    }// moved down as they are not an important part of the interface
    Enumeration(const Enumeration& other) = delete;
    Enumeration(Enumeration&& other) noexcept = delete;
    auto operator=(const Enumeration& other) -> Enumeration& = delete;
    auto operator=(Enumeration&& other) noexcept -> Enumeration& = delete;
};class DataType final: public Enumeration<DataType> {
public:
// Other types...    const std::string_view name;    using Enumeration<DataType>::get; // not needed get is available anyway
public:
    constexpr DataType(const int value, const std::string_view name) noexcept
        : Enumeration<DataType>{value},
        name{name} {}
};class DataType2 final: public Enumeration<DataType2> {
public:
    DataType2(int value)
        : Enumeration<DataType2>{value}
    {
        try {
            const DataType& dt1_b = DataType::get(2);
            std::cout << "DataType::get(2): " << dt1_b.name << std::endl;
        }
        catch (const no_such_enum_value_exception&)
        {
            std::cout << "DataType::get(2) failed" << std::endl;
        }
    }
};static const DataType DT1_A{1, "UNSIGNED INT"};
static const DataType2 DT2_A{1};
static const DataType DT1_B{2, "UNSIGNED INT"};
static const DataType2 DT2_B{2};int main(int,char**){}

Когда вы запустите эту программу, которую вы получите:

DataType::get(2) failed                                                                                                                                                                                                                            
DataType::get(2): UNSIGNED INT                                                                                                                                                                                                                 

Дополнительное видео по вопросу: Java-подобные перечисления в C ++: инициализация статических членов класса

Android 101 by Fred Widjaja