在C ++中使用类似枚举的数据

时间:2010-02-05 22:51:37

标签: c++ enums

我正在更新旧的C ++代码,并且遇到了设计问题,需要有关最佳行动方案的建议。代码处理几何数据。目前,代码定义了许多处理元素类型的全局常量:

#define TETRAHEDRON 0
#define HEXAHEDRON 1

每个常量都有与之相关的信息,该信息保持不变,并且当前由一个类处理,在我们的拓扑中。

int Topology::nodesPerElement(int topType)
{    
    switch(topType) {
        case TETRAHEDRON:
            return 4;
            break;
        case HEXAHEDRON:
            return 8;
            break;
    }
}

Topology类有许多这些函数,它们只需打开全局常量即可找出相关信息。有许多元素类型,并且switch语句引入了许多错误,这些错误不考虑所有元素类型。如果添加了元素类型,则需要修复所有这些方法。我需要一种更好的方法来保持关联信息的类型。

枚举是对此设计的改进,但它没有解决将数据与枚举关联的问题。

为简单起见,我想避免为每种类型实例化类,因为每个类只包含不会改变的静态数据。

我真正需要的是一个“静态类”,它保存这些信息并执行如下伪代码:

class Tetrahedron : public TopType {
    static const int nodesPerElement = 4;
    static const std::string name = "Tet";
    etc...
}

拓扑中的每个方法都变得微不足道:

int Topology::nodesPerElement(TopType topType)
{
    return topType.nodesPerElement;
}

有没有办法在C ++中执行此操作?我已经考虑过删除枚举并为每个拓扑类型分别设置子拓扑类,但我从其他人那里得到的反馈是,它太复杂了。我希望我的问题足够清楚。

10 个答案:

答案 0 :(得分:8)

创建一个包含对象应支持的所有属性的基类,以及一个用于设置这些属性的私有构造函数。您不需要派生类,然后:您可以使用静态公共对象来创建所需的具有所需属性的对象。

class TopologyObject
{
    private:
        int numberVertices;
        int numberFaces;
        // etc.

    public:
        int getVertices() { return numberVertices; };
        int getFaces() { return numberFaces; };

    protected:
        TopologyObject(int vertices, int faces) :
            numberVertices(vertices),
            numberFaces(faces)
        {};

    public:
        static TopologyObject Tetrahedron = new TopologyObject(4, 4);
        // etc.
}

您可以通过TopologyObject::Tetrahedron访问具有所有属性的四面体。

如果您根据对象类型决定需要更复杂的变量行为,那么您确实需要派生类和虚拟方法来实现可重写行为。

答案 1 :(得分:3)

除非您的拓扑类型具有不同的运行时行为(比如绘制自己),否则我同意您的同行认为子分类是过度的。报告nodesPerElement和name之类的静态属性几乎不是运行时行为。

除非你没有告诉我们关于拓扑的整个故事,否则你需要的是一个简单的属性映射。使用std::map将拓扑类型代码与拓扑属性结构相关联。这种重构类似于Replace Subclass with Fields

以下是一些可能作为灵感的代码:

#include <cassert>
#include <iostream>
#include <map>
#include <string>

struct Topology
{
    enum Code {tetrahedron, hexahedron};
    int nodesPerElement;
    std::string name;
};

namespace // Anonymous namespace
{
    // Lookup table associating topology code with properties
    const struct {Topology::Code code; Topology topo;} topoTable_[] =
    {
        {Topology::tetrahedron,  {4, "Tetrahedron"}},
        {Topology::hexahedron,   {6, "Hexahedron"}}
    };
};

class TopologyMap // Singleton
{
public:
    static TopologyMap lookup(Topology::Code code)
    {
        return Topology(instance().doLookup(code));
    }

private:
    typedef std::map<Topology::Code, Topology> Map;
    Map map_;

    TopologyMap()
    {
        // Initialize map with constant property table
        size_t tableSize = sizeof(topoTable_) / sizeof(topoTable_[0]);
        for (size_t row=0; row<tableSize; ++row)
        {
            map_[topoTable_[row].code] = topoTable_[row].topo;
        }
    }

    static TopologyMap& instance()
    {
        static TopologyMap instance;
        return instance;
    }

    const Topology& doLookup(Topology::Code code) const
    {
        Map::const_iterator match = map_.find(code);
        assert(match != map_.end());
        return match->second;
    }
};

class Shape
{
public:
    Shape(Topology::Code topoCode)
    : topo_(TopologyMap::lookup(topoCode)) {}

    const Topology& topology() const {return topo_;}
    // etc...

private:
    Topology topo_;
};

int main()
{
    Shape shape1(Topology::tetrahedron);
    Shape shape2(Topology::hexahedron);
    std::cout << "shape1 is a " << shape1.topology().name << " with " <<
        shape1.topology().nodesPerElement << " nodes per element.\n";
    std::cout << "shape2 is a " << shape2.topology().name << " with " <<
        shape2.topology().nodesPerElement << " nodes per element.\n";
};

输出:

shape1 is a Tetrahedron with 4 nodes per element.
shape2 is a Hexahedron with 6 nodes per element.

如果拓扑代码从零开始并且是连续的,那么您可以使用简单的数组索引而不是映射。但是,如果有人对拓扑代码枚举感到困惑,则数组索引会更容易出错。以下是使用数组索引的相同示例:

#include <cassert>
#include <iostream>
#include <map>
#include <string>

struct Topology
{
    enum Code {tetrahedron, hexahedron, CODE_COUNT};
    int nodesPerElement;
    std::string name;
};

namespace // Anonymous namespace
{
    // Lookup table associating topology code with properties
    const Topology topoTable_[] =
    {
        {4, "Tetrahedron"},
        {6, "Hexahedron"}
    };
};

class TopologyMap // Singleton
{
public:
    static Topology lookup(Topology::Code code)
    {
        assert(code < Topology::CODE_COUNT);
        return topoTable_[code];
    }

private:
    TopologyMap() {} // Non-instantiable
};

class Shape
{
public:
    Shape(Topology::Code topoCode)
    : topo_(TopologyMap::lookup(topoCode)) {}

    const Topology& topology() const {return topo_;}
    // etc...

private:
    Topology topo_;
};

int main()
{
    Shape shape1(Topology::tetrahedron);
    Shape shape2(Topology::hexahedron);
    std::cout << "shape1 is a " << shape1.topology().name << " with " <<
        shape1.topology().nodesPerElement << " nodes per element.\n";
    std::cout << "shape2 is a " << shape2.topology().name << " with " <<
        shape2.topology().nodesPerElement << " nodes per element.\n";
};

请注意,由于存储和检索Topology的详细信息已封装在TopologyMap中,因此我无需重写Shapemain中的任何代码。

答案 2 :(得分:2)

我不确定是谁建议你避免每个Toplogy类型的派生类。在我看来,这个问题是派生类的尖叫。

除非您需要大量此类课程。

答案 3 :(得分:2)

您可以拥有除静态成员变量之外的其他类。这是一种封装属性数据的好方法。

如果你不愿意这样做,traits可能会得到你想要的东西。

答案 4 :(得分:1)

我个人认为存储此信息的最佳方法是创建一个通用的Shape类。然后,不是编写所有这些静态变量,而是将它们放在文件/数据库中,并在启动程序时从数据存储中加载形状信息。

答案 5 :(得分:1)

如果你的目标是避免类实例化,你不能使用记录吗?

但实际上,你应该把这个大便归类。

答案 6 :(得分:1)

如果topType是连续的并且开始为0,那么你可以只维护一个结构数组和索引,而不是尝试拥有类和子类。这样,您需要的唯一代码更改是

  • 添加结构:Easy
  • 添加结构数组:Easy
  • 将每个方法更改为索引到数组并返回struct的正确字段:乏味,但无论如何你必须这样做。

你的拓扑类型可以被建模为结构的一个实例(即没有方法等),类+派生类是过度的,IMO。

答案 7 :(得分:1)

由于(显然)所有相关数据在编译时都可用,因此一种可能性是使用枚举以及模板和专业化来完成工作:

enum { tetrahedron, hexahedron };

template <int type>
struct nodes_per_element { int operator()() const { 
    throw std::invalid_argument("Attempt to use unknown shape");
};

template <>
struct nodes_per_element<tetrahedron> { int operator()() const { return 4; } };

template <>
struct nodes_per_element<hexahedron> { int operator()() const { return 8; } };

您可以使用它:int x = nodes_per_element<hexahedron>()();如果您尝试将其用于没有专门化的值,那将调用非专用模板,这将抛出异常,暂停程序和(通常)显示一条消息,说明您试图使用未知形状。当然,您可以自定义显示方式(如果有的话)。

这应该可以快速显示由于尚未定义的值而导致问题的位置。

另一个显而易见的可能性是为你将要使用的每个形状定义一个结构,并使用形状的名称作为数据的索引以及名称来创建这些结构的数组。您想要的特定数据将是结构的成员。对于您给出的每个元素的节点,看起来像:

struct shape_data { 
    int nodes_per_element;
    std::string name;
};

shape_data data[] = { 
    {4, "Tetrahedron"}, 
    {8, "Hexahedron" }
};

检索数据类似于:

shape_data &s = data[hexahedron];

std::cout << "A " << s.name << " has " << s.nodes_per_element << "nodes per element.\n";

答案 8 :(得分:1)

看了之前的答案,我决定添加自己的答案。

对我来说,我需要这样的设计有两件事:

  • 无需重新编译整个程序即可定义新项目
  • 根据属性(如面部数量)查找项目的能力

这可以很容易做到,所以这里是我的一些代码:

class Solid
{
  typedef std::vector<Solid> solids_type;
public:
  Solid(std::string name, size_t faces, size_t nodes):
    mName(name), mFaces(faces), mNodes(nodes)
  {
  }

  ///
  /// Properties
  ///
  const std::string& getName() const { return mName; }
  size_t getFaces() const { return mFaces; }
  size_t getNodes() const { return mNodes; }

  ///
  /// Collection Handling
  ///
  static bool Add(Solid solid); // only add if it's not already there.

  ///
  /// struct Predicate: std::unary_function<Solid,bool>
  ///
  template <class Predicate>
  static const Solid* Get(Predicate pred)
  {
    solids_type::const_iterator it =
        std::find_if(Solids().begin(), Solids().end(), pred);
    return it == Solids().end()) ? 0 : &(*it);
  } // Get

  ///
  /// Some Predicates
  ///
  class ByName: std::unary_function<Solid,bool>
  {
  public:
    ByName(std::string name): mName(name) {}
    bool operator()(const Solid& s) const { return s.getName() == mName; }
  private:
    std::string mName;
  };

  class ByFaces; /// ...
  class ByNodes; /// ...

private:
  /// Properties
  std::string mName;
  size_t mFaces;
  size_t mNodes;

  /// Collection
  static solids_type& Solids()
  { 
    static solids_type MSolids;
    return MSolids;
  }
}; // class Solid

因此,现在我们可以:

// in tetrahedron.cpp
namespace
{
  bool gTetrahedron = Solid::Add(Solid("Tetrahedron", 4, 4));
}

// in main.cpp
int main(int argc, char* argv[])
{
  const Solid* myTetra = Solid::Get(Solid::ByFaces(4));

  assert(myTetra->getName() == "Tetrahedron");
  assert(myTetra->getFaces() == 4);
  assert(myTetra->getNodes() == 4);

  return 0;
} // main

现在我们已经实现了目标:

  • 添加一个新实体不会导致任何重新编译
  • 我们可以根据属性查找实体

我们也可以想象:

  • 能够遍历所有已注册的实体
  • 让他们按面孔数量或任何
  • 排序
  • 为注册定义一个小宏

答案 9 :(得分:0)

这正是虚拟功能的用途。经典的方法是:

class Topology 
{
  public:

  virtual int nodesPerElement() const = 0;
  // etc
};

class Tetrahedrom : public Topology
{
   public:
   virtual nodesPerElement() const { return 4; }
   // etc
}

// etc

但是如果你真的厌恶重新实现访问器方法(而不仅仅是定义变量),你可以使用模板执行以下操作(尽管它的确非常冗长):

class Topology 
{
  public:

  virtual int nodesPerElement() const = 0;
  // etc
};

template<typename T>
class ConcreteTopology : public Topology
{
  public:

  virtual int nodesPerElement() const { return T::nodesPerElement; }
  // etc
};

struct Tetrahedron_Data {
  int nodesPerElement = 4; 
  // etc
};

typedef ConcreteTypology<Tetraheadron_Data> Tetrahedron;

// etc