实现同时具有枚举和类行为的东西

Implement something that has both enum and class behavior

本文关键字:枚举 实现      更新时间:2023-10-16

考虑一下我有一些同时具有枚举和类行为的"thing"。举个例子,考虑一个有颜色概念的世界,正好有3个:红色、绿色和蓝色。

基于这种颜色,我们也有功能,例如,我们可以有一个功能,告诉我们一种颜色是否是一种快乐的颜色,以及其他一些功能:

isHappy: Color -> {yes, no}
intensity: Color -> value
rotate: Color -> Color

为了完成类似haskel的语法,我们可以这样做:

data Color = Red | Green | Blue

并实现上述功能。但这就是haskell,它不是C++,也没有像C++那样的OO概念。继续C++:

事实上,我们只有3种颜色,而且没有更多,这表明使用枚举;允许我们在源代码中的任何位置使用红色、蓝色和绿色等常量。然而,我们不能将方法添加到枚举中,因此isHappy强度和旋转将作为函数(而不是方法)实现。

事实上,我们有这些方法,其中第一个参数是Color,这表明使用了一个类。然而,我们可以实例化任意多个这样的类,尤其是超过3个的类。这意味着代表红色的两个变量将被分配在内存中的不同位置。这有点奇怪,因为Red将具有非常"恒定"的行为,因为它是不可变的,并且只能创建三种不同类型的Color对象。此外,我们不能使用红色、绿色和蓝色等符号,但需要将它们存储在变量中。使用全局变量将是非常丑陋的imho。

我们也可以使用继承,其中红色、绿色和蓝色继承自Color类。这使我们能够非常容易地微调功能,因为我们可以在任何我们想要的类中实现我们想要的东西。然而,在c++中具有继承性的OO应用了切片。例如,创建一个包含(红色、绿色或蓝色)列表的向量将是非常棘手的。或者创建一个存储3种颜色之一的变量:

Color c1 = Red();
Color c2 = Blue();

变量c1和c2将被分配一个不同的对象,但没有办法真正区分它们。这使得实现操作符==变得棘手:

class Red : Color { //...
  bool operator==(Color &c) const{
     // no way to determine whether c is Red, Green or Blue.
  }
}

对于这种情况,是否有有效的模式或解决方案?我觉得它经常出现,所以我很好奇。仅C++14的解决方案也非常感谢!

编辑:许多人似乎认为我提到的解决方案的问题并不是真正的问题。这是一个有效的观点。然而,我并不是在寻找一个可能的解决方案,我正在寻找一个符合良好的c++(11|14)设计原则的解决方案。我也可以使用:

#define RED 0
#define GREEN 1
#define BLUE 2

这将非常好,但这不是一个好的设计原则,因为它可能会与其他功能发生冲突,使用红色、蓝色或绿色等名称。这在语义上也很奇怪,因为我可以说RED

总之,在答案中,我希望看到一个坚持良好的c++设计原则的解决方案。我不在乎那些有效的解决方案!这可能是前面提到的三种方法之一,使用枚举、一个类或继承。

EDIT2:同时,我还想到了一种基于模板的方法来处理多重继承情况。然而,老实说,我对模板的了解还不足以创造出"好"的东西。这个想法是基于type_traits头的std::is_same和std::is _function等函数。

#include <iostream>
class Color {};
class Red : Color {};
class Green : Color {};
class Blue : Color {};
template<class C1, class C2>
bool is_same_color();
template<class C1>
bool is_happy();
int main() {
    // your code goes here
    return 0;
}

它不起作用,但我希望这个想法能被大家接受。此外,我意识到is_same_color和is_happy应该是定义了运算符()的类。

EDIT3:可以说这就是我想要的:

enum Color {
  RED, GREEN, BLUE
  bool isHappy() { return this==GREEN || this==RED; }
  Color rotate() { return (this==RED ? GREEN (this==GREEN ? BLUE : RED)); }
  int intensity() { return (this==RED ? 11 (this==GREEN ? 12 : 4)); }
}

但这当然不是有效的c++。

您可以使用一个类并为enum:使用特定实例(单个实例)

class Color
{
public:
    bool isHappy() const { return this == &Green || this == &Red; }
    const Color* rotate() const { return (this == &Red ? &Green : (this == &Green ? &Blue : &Red)); }
    int intensity() const {return mIntensity; }
    static const Color* red() { return &Red; }
    static const Color* green() { return &Green; }
    static const Color* blue() { return &Blue; }
private:
    // forbid to construct new instance: use only red(), green(), blue()
    explicit Color(int intensity) : mIntensity(intensity) {}
    Color(const Color&) = delete; 
private:
    int mIntensity;
private:
    static const Color Red;
    static const Color Green;
    static const Color Blue;
};
const Color Color::Red(11);
const Color Color::Green(12);
const Color Color::Blue(4);

已经给出了一些其他不错的答案,但恐怕他们没有充分使用C++表达式和清晰简洁。

我提出以下解决方案:

class Color
{
public:
    virtual bool is_happy() = 0;
    virtual Color* rotate() = 0;
    virtual int intensity() = 0;
    static Color* const Red;
    static Color* const Green;
    static Color* const Blue;
    Color(Color const&) = delete;
private:
    Color(){}
    template<bool happiness, Color* const* rotation_result, int intensity_value>
    class Color_Generator;
    template<bool happiness, Color* const* rotation_result, int intensity_value>
    friend class Color_Generator;
};
template<bool happiness, Color* const* rotation_result, int intensity_value>
class Color::Color_Generator : public Color
{
public:
    bool is_happy()
    {
        return happiness;
    }
    Color* rotate()
    {
        return *rotation_result;
    }
    int intensity()
    {
        return intensity_value;
    }
    static Color_Generator<happiness, rotation_result, intensity_value> Instance;
};
template<bool happiness, Color* const* rotation_result, int intensity_value>
Color::Color_Generator<happiness, rotation_result, intensity_value>
Color::Color_Generator<happiness, rotation_result, intensity_value>::Instance;
Color* const Color::Red = &Color_Generator<true, &Green, 11>::Instance;
Color* const Color::Green = &Color_Generator<true, &Blue, 12>::Instance;
Color* const Color::Blue = &Color_Generator<false, &Red, 4>::Instance;
//==============
// Some usage follows
#include <iostream>
int main()
{
    Color* a = Color::Red;
    Color* b = Color::Green;
    Color* c = Color::Blue;
    std::cout << a->intensity() << std::endl;
    std::cout << b->is_happy() << std::endl;
    std::cout << (b->rotate() == c) << std::endl;
}

还有更多的C++特性可用于进一步改进此代码。例如,您可以使用虚拟继承,以便按照C++的真正精神,将is_happyrotateintensity的定义拆分为它们自己的"facet"类。

两个给定:如果您想要成员函数(看起来合理),则它必须是一个类;以及不同的东西只有在属性(如RedGreenBlue)中才应该有不同的类型。

目前还不清楚为什么要将颜色数量限制为3,但最终,这归结为确保你的类正好有3个实例,不再有了。最简单的这样做的方法是使构造函数私有实例静态成员:

class Color
{
    Color( /* whatever parameters are needed */ );
public:
    static Color red;
    static Color green;
    static Color blue;
    //  ...
};

然后,用户将使用颜色:红色、颜色:绿色和颜色:蓝色(这还有一个额外的优势,那就是你可以做点什么与moods类似,Mood::blue不会引起命名冲突)。

更新:简化代码一点

您可以将其实现为基类Color,它有三个子类RedGreenBlue,并分别具有静态常数REDGREENBLUE

class Red;
class Green;
class Blue;
class Color;
typedef const Color* Color_t;
class Color {
    friend class Red;
    friend class Green;
    friend class Blue;
public:
    static Color_t RED;
    static Color_t GREEN;
    static Color_t BLUE;
    virtual std::string name() const = 0;
private:
    // prohibit instantiation of non-friend subclasses
    virtual ~Color() = default;
    static const Red RED_;
    static const Green GREEN_;
    static const Blue BLUE_;
};
class Red : public Color {
    friend class Color;
private:
    Red() {};  // prohibit instantiation other than by Color
    std::string name() const {return "Red";}
};
class Green : public Color {
    friend class Color;
private:
    Green() {};
    std::string name() const {return "Green";}
};
class Blue : public Color {
    friend class Color;
private:
    Blue() {};
    std::string name() const {return "Blue";}
};
const Red Color::RED_;
const Green Color::GREEN_;
const Blue Color::BLUE_;
Color_t Color::RED = &RED_;
Color_t Color::GREEN = &GREEN_;
Color_t Color::BLUE = &BLUE_;
int main() {
    Color_t c = Color::GREEN;
    c = Color::BLUE;
    if (c == Color::GREEN) {
        std::cout << c-> name() << " is green" << std::endl;
    } else {
        std::cout << c-> name() << " is not green" << std::endl;
    }
    // we can make collections of colors easily
    std::vector<Color_t> colors = { Color::RED, Color::RED, Color::GREEN, Color::BLUE, Color::GREEN };
    return 0;
}

这是的演示

这实际上类似于枚举在Java中的实现方式。

如果你想要的只是一个enum和作用于该enum的函数,以及一种让世界知道这些函数和该enum属于一起的方式,那么namespace可能正是你的工具。

namespace Color
{
enum EColor // or in C++11 use an enum class and set the underlying type to char or such
{
    RED,
    GREEN,
    BLUE
};
bool IsHappy(const EColor & color);
int Intensity(const EColor & color);
EColor Rotate(const EColor & color);
}

您可以很容易地将其创建为具有成员函数的类,因为每个函数都需要一个EColor来处理,但如果不是EColor的每个用户都关心颜色的快乐,那么使用命名空间可能会使您更容易将功能分解为单独的模块,或者你碰巧有一堆与EColor相关的函数,但不需要单个EColor作为参数。