【问题标题】:Implement something that has both enum and class behavior实现具有枚举和类行为的东西
【发布时间】:2014-09-05 12:35:01
【问题描述】:

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

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

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

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

data Color = Red | Green | Blue

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

事实上我们只有 3 种颜色,仅此而已,这表明使用枚举;允许我们在源代码的任何地方使用红色、蓝色和绿色等常量。但是,我们不能向枚举添加方法,因此 isHappy 强度和旋转将被实现为函数(而不是方法)。

我们有这些方法的第一个参数是颜色的事实,建议使用一个类。但是,我们可以实例化任意数量的此类类,尤其是超过 3 个。这意味着代表 Red 的两个变量将被分配到内存中的不同位置。这有点奇怪,因为 Red 将具有非常“类似常量”的行为,因为它是不可变的,并且只能创建三种不同类型的 Color 对象。此外,我们不能使用红色、绿色和蓝色等符号,而是需要将它们存储在变量中。恕我直言,使用全局变量会非常难看。

我们还可以使用继承,其中 Red、Green 和 Blue 从 Color 类继承。这使我们可以非常轻松地微调功能,因为我们可以在任何我们想要的类中实现我们想要的。但是,在 c++ 中具有继承的 OO 应用了切片。例如,创建一个包含(红色、绿色或蓝色)列表的向量会非常棘手。或者创建一个存储三种颜色之一的变量:

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

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

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_functional 等函数。

#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 应该是定义了 operator() 的类。

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++。

【问题讨论】:

  • 我看不出有什么紧迫的理由说明那些不能成为免费函数而必须“成为 OO”,不管这意味着什么。
  • 可能有一个 Color 类,其中嵌入了颜色枚举,并有 RedBlue 等创建适当对象的函数。将它与一些运算符重载(例如operator==)混合,它可能会做你想做的事?
  • “这意味着代表 Red 的两个变量,仍然会将 red1 == red2 评估为 false。” ...为什么?
  • @Herbert 假设您没有实现某些功能,您将不会拥有该功能。这几乎不是一个相关的设计观点。你在问如何设计东西,让它按照你想要的方式运行。 C++ 允许你重载operator ==,因此你应该认为这是可行的。
  • Red 是一个常量值,而不是一个类。颜色可能是一个类。

标签: c++ class c++11 enums


【解决方案1】:

您可以为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);

【讨论】:

  • 做得很好,但在放入 std 容器时仍然需要使用指针,因为据我所知,它们不能包含引用。
  • @Herbert:你可以用std::reference_wrapper代替指针。
【解决方案2】:

已经给出了其他一些不错的答案,但恐怕他们没有充分使用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 的定义拆分为它们自己的“方面”类。

【讨论】:

    【解决方案3】:

    两个给定:如果你想要成员函数(这似乎 合理),那么它必须是一个类;和不同的东西 仅在属性中(如 RedGreenBlue)应该 有不同的类型。

    不清楚为什么要将颜色数量限制为 3, 但最终,这归结为确保您拥有 恰好是您班级的 3 个实例,仅此而已。最简单的 这样做的方法是使构造函数私有,并使 实例静态成员:

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

    然后用户将使用 Color::red、Color::green 和 Color::blue (它还有一个额外的好处是你可以做某事 与情绪相似,并且 Mood::blue 不会引起命名 冲突)。

    【讨论】:

      【解决方案4】:

      更新:稍微简化了代码

      您可以将其实现为基类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;
      }
      

      Here is the demo

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

      【讨论】:

      • 我喜欢这个解决方案!但是,就您的知识和判断而言,它是最好的解决方案吗?为什么它比ideone.com/sQfUtf 更好?
      • @Herbert 因为在这里你有你想要的 - 一个类的行为,由虚函数 name() 演示。
      • 我想好处是我可以在子类或超类中附加功能,避免切换语句处理每种颜色的行为。但是,我将如何表示像 'rgbrrrgbbbrrrbbrr' 这样的红色、绿色和蓝色列表?
      • @Herbert 确切地说,您不必编写 switch 语句。您可以按如下方式列出:std::vector&lt;const Color*&gt; colors = { Color::RED, Color::RED, Color::GREEN, Color::BLUE, Color::GREEN };`
      • 我明白了,很好!虽然遗憾的是需要使用指针才能使用标准容器!
      【解决方案5】:

      如果你想要的只是一个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 作为参数的函数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-02-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多