在VS 2010上使回调接受临时

时间:2010-12-18 12:56:03

标签: c++ visual-studio-2010 callback c++11 rvalue-reference

我有一个回调实现,使用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应用程序中的不同线程中执行函数。)

3 个答案:

答案 0 :(得分:2)

我相信Visual Studio是正确的抱怨,您可以找到解释here

registerFunc中,表达式arglvalue它有一个名称)。如果您想转发它作为右值参考,则必须使用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()>,这就是它的用途。