【问题标题】:C++ functor passing through recursion: "attempt to use a deleted function"C++ 函子通过递归:“尝试使用已删除的函数”
【发布时间】:2014-05-16 02:57:58
【问题描述】:

上下文

数据结构和算法课程的评估部分,练习使用 AVL 树和哈希表解析输入以创建字典文件,然后使用该文件执行粗略的拼写检查。
注意: 寻求帮助来解决这个不是我遇到困难的问题。我正在寻求帮助来理解 C++ 函数对象传递/使用的一个方面,这让我非常沮丧。 C++ 的这方面不属于评估的一部分,没有附加任何标记,我只是在提交代码时遇到了个人问题,我不喜欢它的设计。

问题

将仿函数传递给递归函数会导致编译器错误,“尝试使用已删除的函数”。我认为这是按值传递函子的问题,所以我将参数更改为按引用传递,这会产生“没有匹配的成员函数调用 ”,在这种情况下,我不知道如何更改函数声明以使其匹配。我也尝试过设置参数:const UnaryFunction& action(一个常量函数对象引用),但这会产生编译器错误“no matching function for call to object of type 'const std::__1::__mem_fn<void (DictGen::*)(std::__1::basic_string<char> &)>'”,在这种情况下我不明白为什么它不匹配DictGen::output 签名。

代码

AVL树类的相关部分:

template <class T>
struct AVLNode
{ // simple data carrier node for AVL tree
    AVLNode<T>* lChild;
    AVLNode<T>* rChild;
    AVLBalance balFac;
    T data;
};

template <class T>
class AVLTree<T>
{
    ...

    AVLNode<T>* root;

    template <class UnaryFunction>
    void inorderAction( AVLNode<T>* node, UnaryFunction action )
    {
        if ( node != NULL )
        {
            inorderAction( node->lChild, action );
            action( node->data ); // << problem line
            inorderAction( node->rChild, action );
        }
    }

public:
    template <class UnaryFunction>
    void inorder( UnaryFunction action )
    {
        inorderAction( root, action );
    }
}

DictGen 类的相关部分:

class DictGen
{
    ...

    FILE* outStream;
    AVLTree<std::string> dict;

    void output( std::string& word )
    {
        fprintf( outstream, "%s\n", word.c_str() );
    }

public:
    goGoGadgetDictionaryGenerator()
    {
        ...

        dict.inorder( std::mem_fn( &DictGen::output ) ); // << also problem line
    }
}

口译/笔译

AVL 树类有一个灵活的中序遍历,允许我使用给定的UnaryFunction action 来操作节点。 DictGen 对象使用 FILE* 进行初始化,因此 DictGen 实例可以输出到不同的文件,因此需要在 dict.inorder( ... ) 调用中传递成员函数对象。

迄今为止的努力/研究

我最初的解决方案是遵循我们教科书中给出的函数作为参数示例,其中涉及使用 C 函数指针和污染全局空间。虽然这行得通,但我对这个设计并不满意;我希望将此行为捆绑在一个 DictGen 类中。
在咨询了我的讲师和实验室导师之后,他们建议使用 C++ 仿函数,但由于有一段时间没有使用仿函数,因此无法帮助实现。
我奋力寻找关于 SO 的非常方便的材料(帮助我参考成员函数)、通过 Google 提供的几个仿函数教程以及来自斯坦福课程的关于仿函数实现和使用的优秀 PDF。然而,尽管所有这些资源都让我走到了这一步,但没有人能够阐明我目前的困境。我真的希望将参数设置为 const UnaryFunction&amp; 可以解决它,但不明白为什么签名不匹配。

我也尝试过使用内联 lambda,但需要对象上下文才能访问 outStream

在过去的四天里,我一直在努力解决这个问题,我唯一剩下的线索是一个 SO 帖子,随便说 C++ 规范包含有关函数对象的隐式删除的信息,但我无法取得任何进一步的进展。如果有解决我的问题的 SO 帖子,我找不到它。

问题

递归真的和这个问题有关系吗?
函子传递/使用是否有一些我没有掌握的新手方面?
是什么导致函数被删除?
当函数删除似乎不是问题时,我缺少什么让函数签名匹配?

这是我的第一篇 SO 帖子,我已尽力将提问的建议牢记在心。我欢迎任何建设性的批评来帮助我改进这篇文章,以便它既可以解决我的问题,又可以作为类似问题的未来资源。

【问题讨论】:

  • 这是一个例外第一个问题!欢迎使用 Stack Overflow!
  • 我们还需要查看 AVLNode,因为您将 node-&gt;data 传递给 action(),但不清楚 node-&gt;data 是什么
  • std::mem_fn( &amp;DictGen::output ) 是一个二进制函子,它需要一个指向 DictGen 的引用或指针,然后是对 std::string 的引用。
  • @KennyEvitt 谢谢你,我付出了相当大的努力,希望能保持我所依赖的 SO 内容的质量。

标签: c++11 recursion functor


【解决方案1】:

您需要将一个 DictGen 实例绑定到成员函数:

    // ...
    void gen()
    {
        dict.inorder(
          std::bind( std::mem_fn( &DictGen::output ), 
                     this,  std::placeholders::_1) );
    }
    // ...

【讨论】:

  • 粉碎!这正是我所希望的那种 C++ 能力。在研究&lt;functional&gt; 时,我遇到了bind,但它并没有作为解决方案跳出来。 @perreal,我可以请您解释为什么需要绑定吗?这种需要将成员函数对象重新包装到另一个函数对象中的情况是什么?当我确定需要mem_fn 并弄清楚它的论点时,我假设&amp;DictGen:: 给了它所需的instance-ness(我也尝试过&amp;( this-&gt;output ))。跨度>
  • @exceptionAl,成员函数只能在活动实例上调用。当您执行&amp;DictGen::output 时,您只会获得函数地址。如果你想使用这个指针,你需要提供一个实例地址作为第一个参数(在函数体中变成this)。
  • 我的讲师刚刚对作业范围内的 C++98 施加了限制——这是在作业规范首次上线后几周,并且是在愉快地接受 C++11 提交的先前评估作品之后。我正在重构我的源代码,我花了一天时间与 C++98 bind1st 和 mem_fun/mem_fun_ref 搏斗,但没有成功。您能否阐明如何使您的答案与 C++98 兼容?
【解决方案2】:

您正在使用 C++11 进行编码。虽然 std::mem_fnstd::bind 有一些用途,但它们是生成这类函子的一种非常尴尬的方式。

void gen()
{
    dict.inorder(
      [this]( std::string& word ) { this->output(word); }
    );
}

虽然 lambda 语法对您来说可能有些新意,但它远不如 std::bind( std::mem_fn( &amp;T::method ), this, std::placeholders::_1) 落后

lambda 的基本语法是:

 [capture-list]( arguments )->return value { code }

其中 capture-list 是 [=](按值自动捕获)或 [&amp;](按引用自动捕获)或 [var1, var2](按值捕获 var1 和 var2)或 [&amp;var1, &amp;var2](捕获 var1 和 var2通过参考)或相同的混合物。 (C++1y 增加了新的语法,比如[x = std::move(y)]

(arguments) 只是一个常用的函数参数位。它实际上是可选的,但如果你想要一个返回值,它是必需的。

-&gt; return value 对于单语句 lambdas 或返回 void 的 lambdas 是可选的。 (在 C++1y 中,即使有多个返回也是可选的)

然后是代码。

【讨论】:

  • 谢谢,这也可以。我非常喜欢这个解决方案。我发现上下文和参数声明比使用占位符重新包装函数对象更容易理解。在研究主题领域时,文献绝大多数都偏重于函数指针和函数对象;并且弹出的几个 lambda 示例在捕获列表中没有任何内容,正是缺少的内容。我应该使用哪些搜索词来获得好的 lambda 示例?你能推荐一个好的 C++11 lambda 资源吗?
  • @exceptionAl C++11 FAQ 在 Bjarn 的页面和 C++11 FAQ 在 isocpp 可能是不错的起点。
猜你喜欢
  • 2015-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-26
  • 2016-04-19
  • 2020-05-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多