简体   繁体   English

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

[英]Polymorphism for a class with no virtual functions?

I'm building a parsing tree which works quite simply: there's a base class for Node and the derived classes for different types of nodes.我正在构建一个工作非常简单的解析树:有一个用于 Node 的基类 class 和用于不同类型节点的派生类。 Children nodes are stored in a list<Node*>.子节点存储在列表 <Node*> 中。

The problem arises when you want to traverse such a tree and do some stuff with it.当你想遍历这样一棵树并用它做一些事情时,问题就出现了。 As we know, in C++ you can't perform a downcast if the type is non-polymorphic.正如我们所知,在 C++ 中,如果类型是非多态的,则不能执行向下转型。 However, in my situation I obviously need to be able to downcast from Node to the real class of the child node.但是,在我的情况下,我显然需要能够从 Node 向下转换为子节点的真实 class。 How should I solve this problem?我应该如何解决这个问题? Do I really need to add a useless virtual function just to be able to do that (I don't really need any in my code)?我真的需要添加一个无用的虚拟 function 才能做到这一点(我的代码中真的不需要任何东西)吗? Or is my approach towards designing this structure completely wrong?还是我设计这种结构的方法完全错误?

You can impement polymorphic value types if you want.如果需要,您可以实现多态值类型。

Unless you need insane extendibility, dynamic cast is going to be a poor performing option;除非您需要疯狂的可扩展性,否则动态转换将是一个性能不佳的选择; it is a nuclear bomb to kill a termite.这是杀死白蚁的核弹。

An easy way is to make your node a std::variant of possible node types.一种简单的方法是使您的节点成为可能节点类型的 std::variant。 You can add a "get base class" helper if you need it (and cache it for speed).如果需要,您可以添加一个“获取基类”助手(并缓存它以提高速度)。

For moderate extendibility, a std any is a value type that lets you cast to exact type.对于适度的可扩展性,std any 是一种允许您强制转换为精确类型的值类型。

Or you can implement your own downcast table if you prefer, and keep storing pointers.或者,如果您愿意,您可以实现自己的向下转换表,并继续存储指针。

Myself, I'd use the variant approach.我自己,我会使用变体方法。 Nodes containing nodes takea a bit of finess (as variant needs the full class definition, and a node is a variant of each node type), bit there are a lot of solutions (node is actually a thin class that inherits from, or contains a variant, for example).包含节点的节点需要一点技巧(因为变体需要完整的 class 定义,而节点是每个节点类型的变体),有点有很多解决方案(节点实际上是一个薄的 class 继承自,或包含一个变体,例如)。

Using C++ dynamic cast requires a virtual method.使用 C++ 动态转换需要虚方法。 But you can make the destructor virtual to satisfy that.但是您可以使析构函数成为虚拟的来满足这一点。

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, chicken, duck, flock> {
  using poly_variant<base_avian, chicken, duck, flock>::poly_variant;
};

there.那里。 Now you can现在你可以

std::vector<node> children;
for(auto&child:children){
  child.base().SomeAvianMethod();
  flock* f=std::fet_if<flock>(&child);
}

no vtable in sight.看不到 vtable。

Of course if your base_avian type is empty you can ignore most of this.当然,如果你的 base_avian 类型是空的,你可以忽略大部分。

Just for examples sake, lets say you have a simple set of AST nodes to evaluate (interpret) very simple expressions using only addition and subtraction of integer values.仅作为示例,假设您有一组简单的 AST 节点来评估(解释)非常简单的表达式,仅使用 integer 个值的加减法。

The classes needed for such an AST could be something like this:这样的 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;
    }
};

To evaluate an expression such as 4 + 2 - 6 we would have a tree like this:要评估4 + 2 - 6这样的表达式,我们将有一个这样的树:

SubNode
|-- AddNode
|   |-- ValueNode (4)
|   `-- ValueNode (2)
`-- ValueNode (6)

Or if we set it up in code (assuming suitable constructors):或者如果我们在代码中设置它(假设有合适的构造函数):

Node* root = new SubNode(
    new AddNode(
        new ValueNode(4),
        new ValueNode(2)
    ),
    new ValueNode(6)
);

Then we just call evaluate on the root node to get its result:然后我们只需要在根节点上调用evaluate来得到它的结果:

std::cout << "The result of 4 + 2 - 6 is " << root->evaluate() << '\n';

This will print这将打印

The result of 4 + 2 - 6 is 0

This code, complete and runable, can be found on the compiler explorer here .可以在此处的编译器资源管理器中找到此完整且可运行的代码。

The code can easily be modified to add more types of expressions as well as statements.可以轻松修改代码以添加更多类型的表达式和语句。 And of course to generate code instead of direct evaluation.当然还有生成代码而不是直接评估。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM