1

我有一个回调实现,它使用右值引用来存储与 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 && ./clbck
函数 sample1 这里
函数 sample2 这里
函数 sample2 这里

但是,由于标记的行,当尝试实例化 registerFunc 时,它无法在 VS 2010 中编译:

错误 C2664: 'F::F(FuncType,ArgType &&)' : 无法
使用
[
FuncType=void (__cdecl *)(std::string),
ArgType将参数 2 从 'const char *' 转换为 'const char *&&' =const char *
]
你不能将左值绑定到右值引用

谷歌搜索在 VS2010 上发现了与 Boost 1.44 类似的错误,但推荐的解决方案是根本不使用右值引用。真的没有别的办法了吗?

当您这样做时,我处理这些回调的方式有问题吗?它适用于函数指针和仿函数(我还没有测试过 lambdas),我发现的唯一缺点是上面描述的那个。(请记住,这里显示的代码只是一个小演示,在实际代码中我没有给用户任何指针;我实际上是在使用它在 Qt 应用程序的不同线程中执行函数)。

4

3 回答 3

2

我相信 Visual Studio 的抱怨是对的,你可以在这里找到解释。

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那里会产生一个有趣的警告:

引用成员被初始化为一个临时的,在构造函数退出后不会持续存在

不幸的是,我没有足够的右值引用来进一步帮助您,但我怀疑右值引用成员是否有意义:右值引用绑定到即将死亡的对象,存储它是否有意义?

于 2010-12-18T17:46:15.770 回答
1

关于右值引用成员,您希望实现什么并不完全清楚。只是看起来不太对。您应该避免存储参考。如果您希望函数对象存储类似引用的对象(很像 std::bind 的行为),您仍然可以使用 std::ref 和 std::cref 之类的东西。

您遇到的问题是不允许使用目标类型的左值初始化右值引用。但是您尝试这样做是因为构造函数的参数“arg”是一个命名的右值引用,这使得它成为初始化列表中的左值表达式。您可能使用旧的 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 的参数类型 T&& 具有特殊含义。这是一个“包罗万象”的参数。如果参数是左值,模板参数推导将使 T 成为左值引用(以及 T&& 以及引用折叠规则)。因此,如果您始终希望将参数存储为副本,则必须编写

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));
于 2010-12-18T18:22:10.357 回答
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()>这就是它的用途。

于 2010-12-19T11:34:18.727 回答