【问题标题】:Tree traversal using std::for_each使用 std::for_each 遍历树
【发布时间】:2017-11-29 12:51:20
【问题描述】:

我对在 C++ 中使用 algorithmfunctional 还很陌生。我需要进行树遍历并为每个元素执行一个功能。请参阅下面的代码。

这可行,但我有一些我不喜欢的东西,也许可以做得更好。请注意,我仅限于 g++ (4.4.7) 的相当旧版本,不能使用 lambda 函数。

  1. 我使用包装函数do_walkstd::bind 在每个元素上调用成员函数walk。有没有办法避免包装函数,直接调用成员函数?

  2. 我将 typedef 用于回调函数 UnaryFunction。我更喜欢使用walk 的模板版本。但是,当我更改代码以使用模板时,出现以下编译错误:error: no matching function for call to 'bind(<unresolved overloaded function type>, std::_Placeholder<1>&, void (*&)(const Elem&))'。是否可以在这种情况下使用模板?

  3. 也许std::for_each 的替代品更适合这种树遍历?

到目前为止我的代码:

#include <list>
#include <algorithm>
#include <functional>

struct Elem;
typedef void (*UnaryFunction)(const Elem&); // (2)

struct Elem
{
    std::list<Elem> children; // Some container, std::list for now.

    //template< class UnaryFunction > // (2)
    void walk(UnaryFunction f) const
    {
        // Walk all children.
        std::for_each(
            children.begin(),
            children.end(),
            std::bind(do_walk, std::placeholders::_1, f)); // (1)

        // Walk this object.
        f(*this);
    }

    //template< class UnaryFunction > // (2)
    static void do_walk(const Elem& elem, UnaryFunction f) // (1)
    {
        elem.walk(f);
    }
};

void pretty_print(const Elem& elem)
{
    // Pretty print element.
}

int main()
{
    Elem root;
    // Create tree somehow.
    root.walk(pretty_print);
    return 0;
}

【问题讨论】:

  • 我认为寻求工作代码反馈的问题通常应该发布在 Code Review 上,而不是 SO。
  • @underscore_d 我可以想象这两者都适用,因为它正在询问有关语言功能的特定问题,该功能无法按预期工作(即使问题中存在解决方法) .
  • 你可以使用std::bind(&amp;Elem::walk, std::placeholders::_1, f));,所以你不需要do_walk
  • for range (C++11) 是for_each -> for (auto&amp; elem : children) { elem.walk(f); } 的一个很好的替代品。它应该在 gcc 4.4.7 中启用
  • :/, 所以for (auto it = children.begin(); it != children.end(); ++it) {it-&gt;walk(f);}.

标签: c++ templates stl


【解决方案1】:
  1. std::bind 能够调用成员函数(将第一个参数传递给隐式this 参数),因此您可以将do_walk 替换为:

    std::bind(&Elem::walk, std::placeholders::_1, f)
    
  2. 使walk 成为模板的问题在于,在binding 的时候,并不清楚应该使用哪个实例化。您可以通过显式指定模板参数来消除歧义:

    std::bind(&Elem::walk<UnaryFunction>, std::placeholders::_1, f)
    
  3. 我相信std::for_each 没问题。

[Live example] 使用 gcc 4.4.7

【讨论】:

    【解决方案2】:

    您可以使用 std::function 来解决模板遍历问题。 让我们总结一下:

    我使用包装函数 do_walk 和 std::bind 来调用成员 函数遍历每个元素。有没有办法避免包装 函数并直接调用成员函数?

    是的。使用 std::function

    我对回调函数 UnaryFunction 使用 typedef。我会 更喜欢使用模板版本的 walk。但是,当我更改 使用模板的代码我得到以下编译错误:错误: 调用 'bind(, std::_Placeholder&, void (*&)(const Elem&))' 没有匹配的函数。可能吗 在这种情况下使用模板?

    是的。使用 std::function + 可变参数模板

    也许有一个更适合 std::for_each 的替代方案 对于这种树遍历?

    我认为 std::for_each 可以用于此目的

    #include <list>
    #include <algorithm>
    #include <functional>
    
    
    struct Elem
    {
       std::list<Elem> children; // Some container, std::list for now.
    
                              //template< class UnaryFunction > // (2)
       template<typename ...TArgs>
       void walk( std::function<TArgs...> f ) const
       {
           // Walk all children.
           std::for_each(
               children.begin(),
               children.end(),
               f ); // (1) NO WRAPPER
       }
    };
    
    void pretty_print( const Elem& elem )
    {
        // Pretty print element.
    }
    
    int main()
    {
       Elem root;
       Elem root1;
       root.children.push_back( root1 );
    
       // Create tree somehow.
       root.walk<void( const Elem& )>( pretty_print );
       // or root.walk<decltype( pretty_print )>( pretty_print ); if you do not 
       // want to clarify type
       return 0;
    }
    

    【讨论】:

    • 请注意,std::function 不是免费的,它在每次调用时都有运行时开销(因为调用必须是 virtual)并且不能很好地与内联配合使用。 std::function 在您需要 存储 任意可调用对象时很有用,但当您只需要 调用 一个时则不需要。
    • @Angew:尼特......它不一定是虚拟的,但它几乎肯定是间接的(在 C++ 术语中,虚拟调用或通过函数指针调用)。 libstdc++ 的std::function 是根据两个函数指针实现的(一个实现operator() 的“调用者”和一个负责复制、销毁等的“管理器”)和一个基本上包含函子数据的_Any_data ,如果足够小且不可移动,或者指向它的堆指针。
    • 更正:它不检查 nothrow-movable,因为 std::function 不是 nothrow-movable。我认为这是一个标准缺陷。也和std::any不一致。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-21
    • 1970-01-01
    • 2019-10-20
    • 2022-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多