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

Using an std::function for wrapping a function object

有人可以帮助我理解为什么以下代码会导致错误吗?

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() 中,我 return 引用了 _f 因此,我认为 func() 不是临时的。 如果有人能帮我理解就太好了。

问题不在于 std::function 的使用,而是您试图 return 来自 func() 的临时 float 作为参考。 这将不起作用,因为该对象将在语句结束后立即不复存在。

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

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

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

请注意,对于 std::function<float()> func = a;std::function 使用 a 的副本进行初始化。然后 func() 将 return 引用绑定到 A 的成员,包裹在 func 中,这是一个局部变量。并且当退出函数 foo.

时引用将悬空

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

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

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

解决方法是将 return 类型改为副本(改为 auto),删除引用。

看完上面的精彩回答后,我尝试给出一些不同的想法。

我猜 OP 真的想要 return 某个对象的 float&(在 OP 的例子中是 a)。

所以如果OP想要foo到returnauto&(应该是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;
}