问题描述
我正在构建一个解析树,它的工作非常简单:Node 有一个基类,而不同类型的节点有派生类。子节点存储在列表
当你想遍历这样的一棵树并用它做一些事情时就会出现问题。众所周知,在 C++ 中,如果类型是非多态的,则不能执行向下转换。但是,在我的情况下,我显然需要能够从 Node 向下转换到子节点的真实类。我应该如何解决这个问题?我是否真的需要添加一个无用的虚函数才能做到这一点(我的代码中真的不需要任何虚函数)?还是我设计这种结构的方法完全错误?
解决方法
如果需要,您可以实现多态值类型。
除非您需要疯狂的可扩展性,否则动态转换将是一个性能不佳的选项;杀死白蚁是一颗核弹。
一种简单的方法是使您的节点成为可能的节点类型的 std::variant。如果需要,您可以添加“获取基类”助手(并缓存它以提高速度)。
对于适度的可扩展性,std any 是一种值类型,可让您强制转换为精确类型。
或者,如果您愿意,也可以实现自己的向下转换表,并继续存储指针。
我自己,我会使用变体方法。包含节点的节点需要一些技巧(因为变体需要完整的类定义,而节点是每个节点类型的变体),解决方案有很多(节点实际上是一个继承自或包含变体,例如)。
使用 C++ 动态转换需要一个虚方法。但是您可以将析构函数设为虚拟来满足这一点。
template<class Base,class...Ts> requires (std::is_base_of_v<Base,Ts>&&...)
struct poly_variant:std::variant<Ts...>{
Base& base(){ return std::visit([](auto&&elem)->Base&{return elem;},*this); }
Base const& base()const{ return std::visit([](auto&&elem)->Base const&{return elem;},*this); }
using std::variant<Ts...>::variant;
};
struct node;
// define base_avian
// define duck,chicken,flock
struct node : poly_variant<base_avian,duck,flock> {
using poly_variant<base_avian,flock>::poly_variant;
};
那里。现在你可以
std::vector<node> children;
for(auto&child:children){
child.base().SomeAvianMethod();
flock* f=std::fet_if<flock>(&child);
}
看不到 vtable。
当然,如果您的 base_avian 类型为空,您可以忽略大部分。
,仅作为示例,假设您有一组简单的 AST 节点来计算(解释)非常简单的表达式,仅使用整数值的加减法。
这种 AST 所需的类可能是这样的:
// The basic root node type
struct Node
{
// The (abstract) function to "evaluate" this node
virtual int evaluate() = 0;
};
// Addition and subtraction are *binary* node,which means they each have two child-nodes
// Abstract this using this structure
struct BinaryNode : Node
{
Node* left; // The left-hand side of the binary expression
Node* right; // The right-hand side of the binary expression
};
// This is the node-type representing an addition
struct AddNode : BinaryNode
{
// Here we need to perform something when evaluating the node
// We evaluate each of the left and right nodes,// and return the result of adding them together
int evaluate() override
{
return left->evaluate() + right->evaluate();
}
};
// This is the node-type representing a subtraction
struct SubNode : BinaryNode
{
// Here we need to perform something when evaluating the node
// We evaluate each of the left and right nodes,// and return the result of subtracting them
int evaluate() override
{
return left->evaluate() - right->evaluate();
}
};
// A node-type representing values
struct ValueNode : Node
{
// The actual value of this node
int value;
// The evaluate function just returns the value
int evaluate() override
{
return value;
}
};
要对 4 + 2 - 6
等表达式求值,我们将有一个这样的树:
SubNode |-- AddNode | |-- ValueNode (4) | `-- ValueNode (2) `-- ValueNode (6)
或者如果我们在代码中设置它(假设有合适的构造函数):
Node* root = new SubNode(
new AddNode(
new ValueNode(4),new ValueNode(2)
),new ValueNode(6)
);
然后我们只需在根节点上调用 evaluate
即可获得其结果:
std::cout << "The result of 4 + 2 - 6 is " << root->evaluate() << '\n';
这将打印
The result of 4 + 2 - 6 is 0
此代码完整且可运行,可在 on the compiler explorer here 中找到。
可以轻松修改代码以添加更多类型的表达式和语句。当然还有生成代码而不是直接评估。