cpp中模拟枚举继承的解决方案

Solution to mimic enum inheritance in cpp

我知道枚举继承在 C++ 中是不可能的,但我正在寻找适合我的情况的特定数据结构。假设我有这两个枚举:

    enum Fruit { apple, orange};
    enum Drink { water, milk};

我想要这两个的父级,我可以在这个抽象方法中用作参数

   void LetsEat(Eatable eatable){}

它们将用作简单的开关,基本上我想保持我的代码干净和类型安全。不知会不会强制使用需要初始化的inherited 类。对于这个简单的问题来说太多了。

这听起来像是 std::variant 的绝佳用例。

#include <variant>
#include <iostream>

// Define our enums
enum Fruit { Apple, Orange };
enum Drink { Water, Milk };

// An Eatable is either a Fruit or a Drink
using Eatable = std::variant<Fruit, Drink>;

void letsEat(Eatable eatable) {
  // We can use the index() method to figure out which one we have
  switch (eatable.index()) {
  case 0:
    std::cout << "It's a Fruit!" << std::endl;
    break;
  case 1:
    std::cout << "It's a Drink!" << std::endl;
    break;
  }
}

int main() {
  letsEat(Apple);
  letsEat(Water);
}

请注意,严格来说,std::variant<Fruit, Drink> 不是 FruitDrink 的超类型。相反,它完全是一种新类型,但我们通过其构造函数获得了从 FruitDrink std::variant<Fruit, Drink> 的隐式转换。

如果您不使用 C++17,则可以使用 Boost C++ 库中的 boost::variant

如果你使用C++17或以上,你可以使用std::variant<T, ...>:

#include <iostream>
#include <variant>
#include <type_traits>

enum Fruit { apple, orange };
enum Drink { water, milk };

using Eatable = std::variant<Fruit, Drink>;

void LetsEat(Eatable const eatable) {
    std::visit([] (auto&& v) {
        using T = std::decay_t<decltype(v)>;
        if constexpr (std::is_same_v<T, Fruit>) {
            // Now use it like you would use a normal 'Fruit' variable ...
        }
        if constexpr (std::is_same_v<T, Drink>) {
            // Now use it like you would use a normal 'Drink' variable ...
        }
    }, eatable);
}

int main() {
    LetsEat(apple);
}

或者,您可以只创建一个 class 可以隐式转换为 enum 类型:

class Eatable {
    union {
        Fruit f;
        Drink d;
    } u_;
    bool has_fruit_;
public:
    Eatable(Fruit f) : has_fruit_(true) {
        u_.f = f;
    };
    Eatable(Drink d) : has_fruit_(false) {
        u_.d = d;
    };
    operator Fruit() const {
        return u_.f;
    }
    operator Drink() const {
        return u_.d;
    }
    bool has_fruit() const {
        return has_fruit_;
    }
};

那么你可以这样使用它:

void LetsEat(Eatable const eatable) {
    if (eatable.has_fruit()) {
        Fruit const f = eatable;
        switch (f) {
            case apple:
                std::cout << "Fruit: apple" << std::endl;
                break;
            case orange:
                std::cout << "Fruit: orange" << std::endl;
                break;
            default: break;
        }
    } else {
        Drink const d = eatable;
        switch (d) {
            case water:
                std::cout << "Drink: water" << std::endl;
                break;
            case milk:
                std::cout << "Drink: milk" << std::endl;
                break;
            default: break;
        }
    }
}

笼统地说,enum只是打扮int

enum Fruit { apple, orange};

如果你查看编译后的代码,你会发现 apple 将由值 0 表示,而 orange 将由值 1 表示。

enum Drink { water, milk};

这里也会发生同样的事情。 water 将由值 0 表示,而 milk 将由值 1 表示。您可以从这里开始看到明显的问题。

一个,略显原始的解决方案,相当于在瓷器店放牛:

enum Drink { water=2, milk=3};

现在您可以在传递 int 值的地方做一些事情,并根据它的值找出传递的内容。

但这可能需要大量丑陋的转换,到处都是。生成的代码,如果 posted 到 Whosebug,可能会吸引反对票。

反对票将是因为在现代 post C++17 世界中有更清洁的解决方案。对于初学者,您可以切换到 enum 类.

enum class Fruit { apple, orange};
enum class Drink { water, milk};

这获得了额外的类型安全。将 Fruit 分配给 Drink 不再那么容易了。在许多会发出警告的情况下,您的 C++ 编译器会非常大声地吠叫。您的 C++ 编译器将帮助您在代码中发现更多错误。的确,这需要多打一些字。您将始终必须在任何地方指定具有完整限定的枚举值,即 Fruit::appleDrink::water,而在您现有的代码中仅 applewater 就足够了。但是一些额外输入的字符对于获得更多类型安全的代码以及能够简单地声明来说是一个很小的代价:

typedef std::variant<Fruit, Drink> Eatable;

然后简单地做你一直想做的事:

void LetsEat(Eatable eatable){}

一切都会按照您想要的方式运行。 LetsEat 将接受 FruitDrink 作为其参数。它必须做更多的工作,才能弄清楚 std::variant 中的内容,但从来没有人声称 C++ 很简单。

std::variant 是 C++ 库中较为复杂的模板之一,不可能在 Whosebug 上用一两段文字完整地解释如何使用它。但这是可能的,我会向您推荐您的 C++ 教科书,以获取有关如何使用此模板的完整说明。