使用std::函数包装函数对象

Using an std::function for wrapping a function object

本文关键字:函数 对象 包装 std 使用      更新时间:2023-10-16

有人能帮我理解为什么下面的代码会导致错误吗?

class A
{
  public:
    float& operator()()
    {
     return _f;
    }
  private:
    float _f = 1;
} a;

auto& foo()
{
  std::function<float()> func = a;
  return func();
}
int main()
{
  std::cout << foo() << std::endl;
}

错误:

error: non-const lvalue reference to type 'float' cannot bind to a temporary of type 'float'
  return func();
         ^~~~~~
1 error generated.

这里,在operator()中,我返回对_f的引用,因此,我认为func()不是临时的。如果有人帮我理解就太好了。

问题不在于std::function的使用,而是您试图从func()返回临时float作为引用。这不会起作用,因为一旦语句结束,对象就会不存在。

如果将auto& foo()更改为auto foo(),它应该可以工作。

我想您已经明白,一旦变量超出范围,返回对局部变量的引用是无效的。不过,您似乎缺少的是std::function<float()> func = a;实际上从a创建了一个本地std::function。它没有以任何方式指向afunc有自己的A。这意味着调用func();实际上并没有调用a.operator(),而是调用funcA。然后我们回到局部变量,返回一个引用是邪恶的部分。

为了使其编译,您可以将模板签名更改为float&(),但它仍然是未定义的行为。

修复方法是将返回类型改为副本(auto(,删除引用。

对于std::function<float()> func,您将func声明为返回float的函子,而不是float&。正如错误消息所说,func()返回的临时float不能绑定到非常量左值引用。

上面的声明与正在包装的A::operator()的签名不匹配。但请注意,如果将类型更改为std::function<float&()> func以匹配A::operator()的签名,则可能会解决编译错误,但随后我们将返回绑定到局部变量的引用,从而导致UB。

请注意,对于std::function<float()> func = a;,std::函数是用a的副本初始化的。然后,func()将返回一个绑定到func中封装的A成员的引用,这是一个局部变量。当退出函数foo时,引用将挂起。

如何修复它取决于您的设计,将auto& foo()更改为auto foo(),即通过复制传递返回值可以避免UB。

在阅读了上面的精彩答案后,我试图给出一些不同的想法。

我想OP真的想返回某个对象的float&(在OP的例子中是a(。

因此,如果OP希望foo返回auto&(应该是float&(,则应如下所示,请注意std::bind部分:

namespace T1
{
class A
{
public:
    float& operator()()
    {
        std::cout << "a add = " << this << std::endl;
        return _f;
    }
    float getF() { return _f; }
private:
    float _f = 1;
} a;
auto& foo()
{
    std::function<float&()> func = std::bind(&A::operator(), &a);
    return func();
}
} // end of namespace T1
int main()
{
    std::cout << "global a add = " << &(T1::a) << std::endl; // check a's address
    float& f = T1::foo(); // note that `a`'s address is the same
    std::cout << f << std::endl; // still 1
    f = 777;
    std::cout << f << std::endl; // now 777
    std::cout << T1::a.getF() << std::endl; // it's 777
    return 0;
}