0

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

1 回答 1

2

问题不在于包装谁,std::bind()而在于如何获取重载 [member] 函数的地址!...因为当您尝试使用std::bind().

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

  1. 您尝试获取重载 [member] 函数的地址。如果地址立即与确定确切类型的东西一起使用,编译器将允许这样做,例如,通过将其传递给采用适当 [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。此外,即使它有效,它似乎也创建了函数对象,该对象将修改应该受保护的共享对象,而无需在调用时进行同步。您可能只想在持有锁的同时应用传递的函数对象(请注意,在持有锁的同时应用未知代码通常是一个坏主意,因为当被调用的代码返回时,它会打开创建死锁的机会,并且尝试通过不同的路径访问对象)。

于 2014-12-13T20:38:52.167 回答