在C++结构中处理未知类型的最佳方法是什么?

What is the best way to handle unknown types in a C++ structure?

本文关键字:最佳 方法 是什么 类型 未知 C++ 结构 处理      更新时间:2023-10-16

我正在为一种简单的、类似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指针一样有效?

我能想到两种选择。 一种是使用多态性,其中您有一个抽象的基类、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,它们提供了这样的类。

使用继承/接口:

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

对于enum NodeType中的每个type创建一个继承BaseNode的新类。

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

下面是一个基于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处理动态类型问题。