【问题标题】:Threading a member function with a std::mutex as an argument使用 std::mutex 作为参数对成员函数进行线程化
【发布时间】:2026-01-29 13:40:01
【问题描述】:

所以我有一个对象,它有一个我想要线程化的成员函数。由于这个函数将在对象之外操作一些资源,我想通过引用传递一个互斥锁作为这个函数的参数:

#include <iostream>
#include <mutex>
#include <thread>

class foo
{
    public:
        void bar( std::mutex &m )
        {
            std::lock_guard<std::mutex> lock( m );
            std::cout << "Threading this function works!" << std::endl;
        }
};


int main()
{
    foo a;
    std::mutex m;
    std::thread bar_thread( &foo::bar, std::ref( a ), std::ref( m ) );
    bar_thread.join();
    return 0;
}

这在 Visual Studio 2013/VC++ 中编译和运行良好。但是,当我尝试在 g++ 中编译它时,它失败了。错误信息也很神秘,很难理解编译器在抱怨什么:

/usr/include/c++/4.8/functional: In instantiation of ‘struct std::_Bind_simple<std::_Mem_fn<void (foo::*)(std::mutex&)>(std::reference_wrapper<foo>, std::reference_wrapper<std::mutex>)>’:
/usr/include/c++/4.8/thread:137:47:   required from ‘std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (foo::*)(std::mutex&); _Args = {std::reference_wrapper<foo>, std::reference_wrapper<std::mutex>}]’
thread_test.cpp:63:69:   required from here
/usr/include/c++/4.8/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<void (foo::*)(std::mutex&)>(std::reference_wrapper<foo>, std::reference_wrapper<std::mutex>)>’
      typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                            ^
/usr/include/c++/4.8/functional:1727:9: error: no type named ‘type’ in ‘class std::result_of<std::_Mem_fn<void (foo::*)(std::mutex&)>(std::reference_wrapper<foo>, std::reference_wrapper<std::mutex>)>’
        _M_invoke(_Index_tuple<_Indices...>)
        ^

我怀疑它与 std::mutex 的不可复制性有关,也许 g++ 中的 std::ref 实现与 vc++ 中的不同?不过,这只是一个随机猜测。

有没有人精通这两种不同 C++ 编译器的精妙之处,知道是什么导致了这个问题,以及如何解决这个问题?

【问题讨论】:

  • @RichardHodges g++ 4.8.4

标签: c++ multithreading c++11 g++ mutex


【解决方案1】:

将 reference_wrapper 作为 INVOKE 的“this”参数传递,其中 f 是成员函数指针,这是 c++17 的标准。

在那之前,它不是严格有效的。

替代方案:

#include <mutex>

#include <thread>
#include <iostream>

class foo
{
    public:
        void bar( std::mutex &m )
        {
            std::lock_guard<std::mutex> lock( m );
            std::cout << "Threading this function works!" << std::endl;
        }
};


int main()
{
    foo a;
    std::mutex m;
    std::thread bar_thread( &foo::bar, std::addressof( a ), std::ref( m ) );
    bar_thread.join();
    return 0;
}

推理:

std::thread::thread&lt;&gt;(f, args...)是按照INVOKE(f, args...)来实现的

这里有一些参考资料:

http://en.cppreference.com/w/cpp/concept/Callable

【讨论】:

  • 还有一件事,当函数不将互斥锁作为参数时,如何使用 std::ref() ?还是仅在函数接受参数时才根据 INVOKE 实现?
  • @Fat-chunk 我不确定你的意思?你是说你修改了 bar 让它不带参数吗?
  • 为什么在这种特殊情况中使用addressof而不是&amp;a?只是为了说明?
  • @RichardHodges 是的,这就是我的意思。在这种情况下,使用 std::ref(a) 编译并工作。
  • @sergey 是的,那会是更好的形式。
【解决方案2】:

当传递指向对象的指针而不是引用(包装器)时,这会在 g++ 中编译:

std::thread bar_thread( &foo::bar, &a, std::ref( m ) );

显然,正如 Richard Hodges 所回答的那样,直到 C++17 才支持引用包装器作为被调用者。

【讨论】:

  • 这不是错误,除非它不能与 -std=c++17 一起使用
  • @RichardHodges 我不熟悉标准的那部分,但我相信你和 cppreference 的话。
  • 我认为这更像是历史的意外。只是在以前的任何标准中都没有提到作为虚构 INVOKE 函数的 arg2 的引用包装器(大概是因为没有人认为他们需要它)。 std::invoke() 自 c++17 起作为函数存在。因此,它进行了整容。