【发布时间】:2018-03-11 08:57:35
【问题描述】:
我试图玩弄 std::function 和 std::bind ,但我遇到了一个问题。我想构建一个通用结构,允许我将 std::function 绑定到成员函数,而无需先验地知道成员函数的参数。我写了这个东西
template<typename Class, typename Return, typename ...Args>
struct Caller
{
private:
std::function<Return(Args ...)> callerFunction;
Caller(const Caller&) = delete;
Caller(Caller&&) = delete;
Caller& operator=(const Caller&) = delete;
public:
~Caller() = default;
Caller() = default;
Caller(Class& instance, Return(Class::*function)(Args...))
{
callerFunction = [&](Args... args) { return (instance.*function)(args...); };
}
Return operator() (Args ... args)
{
return callerFunction(args...);
}
};
仅供参考,我知道函数的参数是按值传递的(我在使用带有可变参数模板的通用引用时遇到了一些问题,我稍后会处理)。
这里的问题是,当我使用 operator() 触发函数时,我得到了访问冲突错误。我试图缩小问题范围并创建一个没有可变参数的结构(允许成员函数只有一个 int 作为参数),我看到将 lambda 分配给 std::function 给了我同样的错误,但是如果我使用 std::bind 和占位符一切都很好。
试验场是这里
class A
{
public:
bool foo(int a)
{
std::cout << a << std::endl;
return true;
}
};
int main()
{
A a;
a.foo(9);
Caller<A, bool, int> caller(a, &A::foo);
caller(10);
std::cin.ignore();
}
使用 lambda,是否需要保存类的实例才能正确调用成员函数?
【问题讨论】:
-
我很确定你有 UB。在
Caller的构造函数中,您按值传递成员函数指针(因此将创建仅存在于构造函数内部的指针副本),然后通过引用在 lambda 中捕获它。因此,当您调用caller(10)时,指针已超出范围。instance在此示例中很好,因为您通过引用将其传递给构造函数,并且当您调用caller(10)时,a仍在范围内。 (尽管您应该假设将来会发生变化并围绕它进行设计。) -
还值得注意的是,已经存在一个
std::mem_fn应该可以与 lambda 或std::bind一起使用。 -
好的,明白了。我不明白如何解决这个问题。关于实例:在程序启动时,我将实例化一些类,这些变量将在程序结束之前可用。
-
@0x5453 你指的UB是当我使用bind而不是lambda的时候?
-
UB 正在读取超出范围的变量(成员函数指针)。要修复,您可以按值捕获指针,以便 lambda 将拥有它自己的指针副本,不会超出范围。对于
instance,只要您在任何地方都保持不变,就可以了。可能也会留下一些cmets,这样你未来的自己就不会忘记。 (如果您确实希望 lambda 比对象更长寿,我可能会传递并按值捕获std::shared_ptr<Class>,这样它就可以在不创建副本的情况下继续存在,因为Class可能会非常大。)