如何为std :: bind创建一个包装?

时间:2014-12-13 20:04:47

标签: c++ c++11 stdbind

我正在构建一个简单的类来执行原子操作,但是当我尝试调用方法call时出现错误

#include <iostream>
#include <string>
#include <mutex>
#include <functional>

template<typename _Tp>
class Atom {
  _Tp val_;
  std::mutex mtx_;

public:
  Atom() = default;
  ~Atom() = default;
  Atom(const Atom&) = delete;
  Atom& operator=(const Atom&) = delete;
  Atom& operator=(const Atom&) volatile = delete;

 // FIXME:
 template<typename Ret, typename... _Args>
  Ret call(_Tp&& f, _Args&&... args) {
    mtx_.lock();

    auto b_fn = std::bind(static_cast<Ret(_Tp::*)(_Args...)>(f), 
                std::ref(val_), std::forward<_Args>(args)...);

    Ret r = b_fn();
    mtx_.unlock();
    return r;
  }

  operator _Tp() {
    return load();
  }

  _Tp operator= (_Tp val) {
    store(val);
    return val;
  }

  _Tp load() {
    _Tp tmp;
    mtx_.lock();
    tmp = val_;
    mtx_.unlock();
    return tmp;
  }

  void store(_Tp val) {
    mtx_.lock();
    val_ = val;
    mtx_.unlock();
  }
};

我尝试这样使用:

int main(int argc, char **argv) {
  Atom<std::string> str;
  str = "asdf";
  // FIXME:
  str.call(&std::string::append, std::string("test"));

  std::string ot = str;
  std::cout << ot << std::endl;
  return 0;
}

错误:

error: no matching function for call to ‘Atom<std::basic_string<char> >::call(<unresolved overloaded function type>, std::string)’
       str.call(&std::string::append, std::string("test"));

1 个答案:

答案 0 :(得分:2)

问题不在于如何包装std::bind()而不是如何获取重载[member]函数的地址! ...因为当您尝试使用std::bind()时,您会遇到完全相同的问题。

您尝试做的事实上存在问题:

  1. 您尝试获取重载[member]函数的地址。如果地址立即与确定确切类型的东西一起使用,编译器将允许,例如,通过将其传递给采用适当的[成员]函数指针的函数或通过强制转换它。如果它不是第二个问题,你可以使用这样的东西:

    static_cast<std::string& (std::string::*)(std::string)>(&std::String::append)
    

    演员表会推断出正在使用哪种超载。

  2. 该标准赋予标准库明确的权限,以实现类和类模板的任何非virtual成员函数,以获取任意默认的附加参数。但是,在获取其中一个地址时,您需要知道确切的参数才能使[member]函数指针匹配。

  3. 因此,尝试std::bind()标准C ++库中的一个类成员既相​​当冗长又不可移植。最简单的方法可能只是使用lambda函数:

    std::string test("test")
    str.call([=](std::string& s){ return s.append(test); });
    

    (显然,为了附加一个字符串文字,你不需要额外的变量,但我想提供一个非空闭包的例子)。另一种方法是创建一个合适的函数对象并传递它而不是尝试指向成员函数的指针:

    struct append {
        template <typename... Args>
        auto operator()(std::string& s, Args&&.. args) const
          -> decltype(s.append(std::forward<Args>(args)...)) {
            return s.append(std::forward<Args>(args)...);
        }
    };
    
    str.call(append(), std::string("test));
    

    我意识到这两种方法都不像你想象的那样方便,但目前C ++还没有一种很好的方法来创建像上面那样的函数对象。如果我们有一些东西会很好,但我甚至不知道相应的提案。

    顺便说一下,您的call()功能仍然无法正常工作,因为没有适当的推断返回类型Ret。此外,即使它工作,它似乎创建功能对象,它将修改假定受保护的共享对象,而无需在调用时进行同步。你可能只想在持有锁的同时应用传递的函数对象(注意,在持有锁时应用未知代码通常是一个坏主意,因为当被调用的代码返回时它开启了创建死锁的机会。试图通过不同的路线访问该对象。)