5

我正在为一种简单的、类似 Lisp 的编程语言编写解释器。它将代码处理成节点,所有节点都有类型,其中一些可能有索引顺序的子节点。由于信息性质的不同,我不能对所有节点值使用相同长度的类型。它们类型的名称是枚举类型,但我对值类型的唯一想法是void *. 但是当我使用这个时,我认为我必须非常小心。我的意思是,我不能使用任何默认析构函数,我必须编写一个关心节点类型的析构函数。此外,即使访问值,我也必须使用大量强制转换。

这就是我所说的:

enum NodeType {/* Some node types */}

class Node
{
public:
    Node(string input_code);
private:
    NodeType type; // Having this I can know the type of value
    void* value;
};

有没有一种更安全的方法,可以编写更好的代码,但仍然与使用 void 指针一样有效?

4

4 回答 4

6

我能想到的有两种选择。一种是使用多态性,其中您有一个抽象基类Node和许多特定于类型的子类。也许是这样的:

class Node
{
public:
  virtual ~Node() = 0;
};

class String : public Node
{
public:
  ~String() {}
};

class Float : public Node
{
public:
  ~Float() {}
};

存储这些节点时,您将存储Node*而不是void*. 基类中(抽象)虚拟析构函数的存在使得可以通过基类指针正确销毁具体对象,如下所示:

Node* obj = new String;
delete obj;

如果这些方法在基类中是虚拟的,您还可以调用基类中声明的方法并让它们在正确的派生类中执行代码。这些通常也是纯虚拟的,例如:

class Node
{
public:
  std::string Speak() const = 0; // pure virt
};

class String : public Node
{
public:
  std::string Speak() const { return "Hello"; }
};

另一种选择是使用某种变体类。C++ 本身没有内置到语言中的变体类,但是已经编写了一些库,例如 Boost,它们提供了这样的类。

于 2012-11-29T14:23:57.850 回答
2

使用继承/接口:

struct BaseNode {
    std::vector<BaseNode*> _children;
    /*Some functions here*/
}

为每type一个enum NodeType创建一个继承的新类BaseNode

于 2012-11-29T14:17:50.527 回答
0

您需要使用某种变体类型。Boost 有一个,详细信息可以在这里找到。

于 2012-11-29T14:16:11.907 回答
0

这是一个boost::variant基于节点系统的快速草图:

class Node;
struct Empty {};

// Keep enum and variant in sync:
enum NodeType {
  eEmptyNode,
  eStringNode,
  eIntNode,
  eListNode,
};
typedef std::vector< const Node > NodeList;
typedef boost::variant< std::string, int, NodeList > NodeData;

// Keep this in sync with types in Node and enum:
NodeType GetNodeType( Empty const& ) { return eEmptyNode; }
NodeType GetNodeType( std::string const& ) { return eStringNode; }
NodeType GetNodeType( int const& ) { return eIntNode; }
NodeType GetNodeType( NodeList const& ) { return eListNode; }


// Some helper code:
struct GetNodeType_visitor
{
  typedef NodeType return_type;
  template<typename T>
  NodeType operator()( T const& t ) const { return GetNodeType(t); }
};

template<typename T, typename Function>
struct OneType_visitor
{
  typedef bool return_type;
  Function func;
  OneType_visitor( Function const& f ):func(f) {}
  template<typename U>
  bool operator()( U const& u ) const { return false; }
  bool operator()( T const& t ) const { func(t); return true; }
};

struct Node
{
  NodeData data;
  NodeType GetType() { return boost::apply_visitor( GetNodeType_visitor, data ); }
  template<typename T, typename Function>
  bool Apply( Function const& func ) const
  {
    return boost::apply_visitor( OneType_visitor<T>(func), data );
  }
  template<typename T>
  Node( T const& t ):data(t) {}
  Node():data(Empty()) {}
};

// example usage:
int main()
{
  NodeList nodes;
  nodes.push_back( Node<int>( 7 ) );
  nodes.push_back( Node<std::string>( "hello" ) );
  Node root( nodes );

  Assert( root.GetType() == eListNode );
  std::function<void(Node const&)> PrintNode;
  auto PrintInt = [](int const& i) { std::cout << "#" << i; };
  auto PrintString = [](std::string const& s) { std::cout << "\"" << s << "\""; };
  auto PrintList = [&](NodeList const& list) {
    std::cout << "[";
    for (auto it = list.begin(); it !=list.end(); ++it)
    {
      if (it != list.begin()) std::cout << ",";
      PrintNode( *it );
    }
    std::cout << "]";
  }
  auto PrintEmpty = [](Empty const&) { std::cout << "{}"; }
  PrintNode = [&](Node const& n)
  {
    bool bPrinted = false;
    bPrinted = n.Apply<int>( PrintInt ) || bPrinted;
    bPrinted = n.Apply<std::string>( PrintString ) || bPrinted;
    bPrinted = n.Apply<NodeList>( PrintList ) || bPrinted;
    bPrinted = n.Apply<Empty>( PrintEmpty ) || bPrinted;
    Assert(bPrinted);
  }
  PrintNode(root);
}

代码未经测试,但基本思想应该成立。

请注意,我使用的是不可变节点,因为这是用于类似 lisp 的语言。真的我应该使用std::shared_ptr<const Node>或类似的东西,所以两棵树可以共享数据。

boost::variant处理动态类型问题。

于 2012-11-29T15:25:30.787 回答