указатели — Каков наилучший способ обработки неизвестных типов в структуре C ++?

Я пишу интерпретатор для простого, похожего на Лисп языка программирования. Он будет обрабатывать код в узлах, все они имеют типы, и некоторые из них могут иметь дочерние узлы в индексированном порядке. Из-за различий в характере информации я не могу использовать одинаковую длину типа для всех значений узлов. Имя их типа является типом enum, но у меня есть единственная идея для типа значений: 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;
};

Есть ли способ, который был бы более безопасным, создавал бы лучший код, но при этом был бы так же эффективен, как использование пустых указателей?

4

Решение

Есть два варианта, которые я могу придумать. Одним из них является использование полиморфизма, в котором у вас есть абстрактный базовый класс, 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, которые предоставляют такой класс.

5

Другие решения

Используйте наследование / интерфейсы:

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

Для каждого type в enum NodeType сделать новый класс, который наследует BaseNode,

2

Вам нужно использовать какой-то тип варианта. Boost имеет один и детали можно найти Вот.

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 обрабатывает проблему динамического набора текста.

0
По вопросам рекламы [email protected]