如何为 std::bind 制作包装

How to make a wrap for std::bind?

本文关键字:包装 bind std      更新时间:2023-10-16

我正在构建一个简单的类来执行原子操作,但是当我尝试调用该方法时出现错误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"));

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

您尝试执行的操作实际上存在问题:

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

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

    强制转换将推断正在使用哪个过载。

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

因此,尝试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.此外,即使它有效,它似乎也会创建函数对象,该对象将修改应该受保护的共享对象,而无需在调用时同步。您可能只想在持有锁的同时应用传递的函数对象(请注意,在持有锁的同时应用未知代码通常是一个坏主意,因为它在被调用的代码返回并尝试通过不同的路由访问对象时打开了创建死锁的机会(。