我正在为一个简单的,类似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指针一样高效?
答案 0 :(得分:5)
我能想到两种选择。一种是使用多态,其中有一个抽象基类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,它们提供了这样的类。
答案 1 :(得分:2)
使用继承/接口:
struct BaseNode {
std::vector<BaseNode*> _children;
/*Some functions here*/
}
type
中的每个enum NodeType
都会创建一个继承BaseNode
的新类。
答案 2 :(得分:0)
您需要使用某种变体类型。 Boost有一个,可以找到详细信息here。
答案 3 :(得分: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
处理动态类型问题。