【问题标题】:Call base method from base function pointer with arguments从带有参数的基函数指针调用基方法
【发布时间】:2019-12-17 10:36:14
【问题描述】:

我正在使用访问者模式来遍历和打印我正在操作的树的子节点。为了获得缩进打印,我以如下样式指定缩进级别:

printCurrent();
indentLevel();        // increases static variable
Visitor::visit(elem); // which then prints the children's node data
unindentLevel();      // decreases static variable

为了使这更好,我想实现一个函数,该函数采用 Visitor::visit 和参数 elem 并自动处理预操作 (indentLevel()) 和操作后 (unindentLevel())。

在实现该函数之前,我需要定义一个函数指针,该指针将用作函数的参数。但是,我未能指定指针的参数。作为一个例子,让我们看看从Visitor派生的PrintVisitor

void PrintVisitor::visit(BinaryExpr &elem) {
    std::cout << formatOutputStr({elem.getNodeName()});
    this->incrementLevel();
    Visitor::visit(elem);    // <-- this is where I want to create a function pointer to
    this->decrementLevel();
}

这个想法基本上是PrintVisitor 完成与打印相关的所有事情,所有其他逻辑(例如遍历逻辑)都在基类Visitor 中实现。因此,PrintVisitor::visit 需要执行其特定的操作(例如,通过 formatOutputStr 打印),然后执行 Visitor::visit 方法:

void PrintVisitor::visit(BinaryExpr &elem) {
    std::cout << formatOutputStr({elem.getNodeName()});

    void (Visitor::*myPt)(BinaryExpr&) = &Visitor::visit;  // declare function pointer
    executeIndented(myPt, elem);  // pass function pointer myPt
}
// ...

void executeIndented("Function f", "FunctionArgs elem") {
   // pre-action
    this->incrementLevel();

   // main action: call function pointer
   (Visitor().*f)(elem);  // call function pointer with arg

   //post-action
   this->decrementLevel();    
}

我的目标是以某种方式实现在每个 PrintVisitor::visit 方法中始终调用前操作和后操作。为此,我认为将这些前置和后置操作封装到另一个函数 executeIndented 中是有意义的。

(Visitor().*myPt)(elem); 的语法在我看来有点奇怪,这真的是使用我的函数指针 myPt 调用带有参数 elem 的(基本)函数 Visitor::visit 的正确方法吗?

// 编辑 使用 (Visitor(*this).*myPt)(elem); 也可以。这两种方式有什么区别,哪一种是首选?

// EDIT2 希望现在对我要实现的目标的描述更加清晰。

【问题讨论】:

  • 这能回答你的问题吗? C++ Call Pointer To Member Function
  • Visitor(*this) 进行对象切片复制。 Visitor() 使用默认构造函数...
  • @Patrick,这很重要,因为Visitor(*this) 是复制构造函数调用,Visitor() 是上面提到的默认构造函数,所以两个对象可能有不同的状态(取决于那里的构造函数),这两个调用也是从PrintVisitor 创建不同类型的Visitor 对象是你想要的吗?或者您只想从PrintVisitor 调用基类visit 函数。我不确定我是否正确理解了您的原始问题。
  • @Patrick 从PrintVisitor 调用Visitor::visit(elem);? 有什么问题,因为PrintVisitor 派生自Visitor
  • 仅供参考:(我的回答)SO: Preorder traversal through Morse code BST,重点关注BSTreeT::traverse()

标签: c++ function-pointers


【解决方案1】:

据我了解,您应该有类似的东西:

struct TraversalVisitor : IVisitor
{
    void visit(BinaryExpr &elem) final
    {
        pre_traversal_action(elem);
        visit(elem.lhs);
        action(elem);
        visit(elem.rhs);
        post_traversal_action(elem);
    }
    virtual void pre_traversal_action(BinaryExpr &elem) { /*Nothing */ }
    virtual void action(BinaryExpr &elem) { /*Nothing */ }
    virtual void post_traversal_action(BinaryExpr &elem) { /*Nothing */ }

    void visit(UnaryExpr &elem) final;
    // ...
};

struct PrintVisitor : TraversalVisitor
{
    void pre_traversal_action(BinaryExpr &elem) override { 
        std::cout << formatOutputStr({elem.getNodeName()});
        incrementLevel();
    }
    //void action(BinaryExpr &elem) override { /*Nothing */ }
    void post_traversal_action(BinaryExpr &elem) override { decrementLevel(); }
    // ...
private:
    void formatOutputStr(const std::string&);
    void incrementLevel();
    void decrementLevel();
    // ...
};

而你尝试实现类似的东西:

struct Visitor : IVisitor
{
    virtual visit(BinaryExpr &elem)
    {
        visit(elem.lhs);
        visit(elem.rhs);
    }
// ...
};

struct PrintVisitor : Visitor
{
private:
    void formatOutputStr(const std::string&);
    void incrementLevel();
    void decrementLevel();

    void executeIndented(Expr& elem) {
        incrementLevel(); // pre-action

        // Traversal
        Visitor::visit(elem);

        decrementLevel(); // post-action
    }

    void visit(BinaryExpr &elem) override {
        std::cout << formatOutputStr({elem.getNodeName()});

        executeIndented(elem);
    }

    // ...
};

IMO,您的尝试只是分解PrintVisitor,而不执行一些遍历策略。

【讨论】:

  • 这与我的意图非常接近,可以使用,谢谢!我最初的想法是在visit 方法中直接执行action:而不是拥有单独的pre_traversal_actionpost_traversal_action,而是拥有一个单独的方法pre_post_traversal(Function f),它采用函数f(例如@ 987654330@) 及其 args (elem) 然后 (1) 应用预遍历操作,(2) 运行函数 f,并运行 (3) 后遍历操作。这样做的目的是强制执行后遍历操作,而不是要求在每个 visit(...) 方法中指定它。
  • 我当前的版本需要 node 执行前/后操作,但很容易被任何节点共享:virtual void post_traversal_action(BinaryExpr &amp;) -> virtual void post_traversal_action(IExpr&amp;)
  • 添加了一个版本,它应该是您尝试实现的更多内容。
  • 你是个天才!这正是我想要达到的目标——不过要复杂得多。非常感谢您的耐心和帮助。我需要承认您的第一个解决方案似乎更合理,更通用。我想我会尝试将这两种解决方案结合起来。
猜你喜欢
  • 1970-01-01
  • 2017-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-16
  • 1970-01-01
  • 2020-04-14
  • 2011-04-01
相关资源
最近更新 更多