没有虚函数的类的多态性?

问题描述

我正在构建一个解析树,它的工作非常简单: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 中找到。

可以轻松修改代码以添加更多类型的表达式和语句。当然还有生成代码而不是直接评估。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...