我有一个回调实现,使用rvalue引用来存储适用于gcc的参数,但是在某些代码中无法在VS 2010中编译。简短版本:
#include <iostream>
#include <string>
class B {
public:
virtual void execute() = 0;
};
template<typename FuncType, typename ArgType>
class F : public B {
public:
F(FuncType func, ArgType && arg) : f(func), arg(arg) {}
void execute() { f(arg); }
private:
FuncType f;
ArgType && arg;
};
template<typename FuncType, typename ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
return new F<FuncType, ArgType>(func, arg);
}
void myFunction(std::string text)
{
std::cout << "Function " << text << " here" << std::endl;
}
int main()
{
const char text1[] = "sample1";
std::string text2("sample2");
B * b = registerFunc(myFunction, text1);
b->execute();
delete b;
b = registerFunc(myFunction, text2);
b->execute();
delete b;
// VS 2010 fails because of this call
b = registerFunc(myFunction, text2.c_str());
b->execute();
delete b;
return 0;
}
使用gcc 4.4,这会产生:
$ g ++ clbck.cpp -std = c ++ 0x -o clbck&amp;&amp; ./clbck
函数sample1在这里
功能样本2在这里 函数sample2在这里
然而,由于标记的行,尝试实例化registerFunc时无法在VS 2010中编译:
错误C2664:'F :: F(FuncType,ArgType&amp;&amp;)':无法将参数2从'const char *'转换为'const char *&amp;&amp;'左右 与
[
FuncType = void(__ cdecl *)(std :: string),
ArgType = const char *
]
您不能将左值绑定到右值引用
谷歌搜索在VS2010上发现了与Boost 1.44类似的错误,但推荐的解决方案根本不使用右值引用。真的没有别的办法吗?
当你处理它时,我处理这些回调的方式有问题吗?它适用于函数指针和函子(我还没有测试lambdas),我找到的唯一缺点是上面描述的那个。 (请记住,此处显示的代码只是一个小型演示,在实际代码中我没有给用户任何指针;我实际上是使用它来在Qt应用程序中的不同线程中执行函数。)
答案 0 :(得分:2)
我相信Visual Studio是正确的抱怨,您可以找到解释here。
在registerFunc
中,表达式arg
是lvalue
(它有一个名称)。如果您想转发它作为右值参考,则必须使用std::forward
:
template<typename FuncType, typename ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
return new F<FuncType, ArgType>(func, std::forward<ArgType>(arg));
}
FuncType
构造函数中出现同样的问题,但在其中添加std::forward
会产生一个有趣的警告:
引用成员被初始化为一个在构造函数退出后不会持久化的临时成员
不幸的是,我没有足够的rvalue引用来帮助你进一步,但我怀疑rvalue引用成员是否有意义:rvalue引用绑定到即将死亡的对象,它是否有意义存储它?
答案 1 :(得分:1)
关于rvalue参考成员,您希望实现的目标并不完全清楚。它看起来不对劲。您应该避免存储引用。如果希望函数对象存储类似引用的对象(很像std :: bind行为),你仍然可以使用std :: ref和std :: cref之类的东西。
您遇到的问题是不允许使用目标类型的左值初始化右值引用。但是你试图这样做是因为构造函数的参数“arg”是一个名为的rvalue引用,它使它成为初始化列表中的左值表达式。您可能使用旧的GCC版本来编译此代码。较新的版本也会抱怨这一点。
依靠std :: bind:
可以省去一些麻烦template<class FuncType>
class F : public B {
public:
explicit F(FuncType func)
: f(std::forward<FuncType>(func))
{}
void execute() { f(); }
private:
FuncType f;
};
....
auto fun = std::bind(myFunction,text2.c_str());
B* ptr = new F<decltype(fun)>(fun);
如果你真的想自己处理参数绑定,你应该这样做:
template<class FuncType, class ParamType>
class F : public B {
public:
F(FuncType func, ParamType para)
: f(std::forward<FuncType>(func))
, p(std::forward<ParamType>(para))
void execute() { f(p); }
private:
FuncType f;
ParamType p;
};
请记住参数类型T&amp;&amp;其中T可以推导出具有特殊含义。这是一个“全能”参数。如果参数是左值,模板参数推导将使T成为左值参考(以及T&amp;&amp;以及参考折叠规则)。因此,如果您始终希望将参数存储为副本,则必须编写
template<class FuncType, class ArgType>
B * registerFunc(FuncType func, ArgType && arg)
{
typedef typename std::decay<ArgType>::type datype;
return new F<FuncType,datype>(func, std::forward<ArgType>(arg));
}
我更喜欢这样的registerFunc函数。它默认复制功能对象和参数对象。覆盖它可以通过std :: ref和std :: cref:
来完成registerFunc(some_func,std::cref(some_obj));
答案 2 :(得分:0)
为什么不使用lambda表达式?
class B {
virtual void execute() = 0;
};
template<typename T> class F : public B {
T t;
public:
F(T&& arg) : t(std::forward<T>(arg)) {}
void execute() { return t(); }
};
template<typename T> F<T> make_f(T&& t) {
return F<T>(std::forward<T>(t));
}
int main() {
std::string var;
B* callback = make_f([=]() {
std::cout << var << std::endl;
});
}
或者,确实是std::function<void()>
,这就是它的用途。