1
class test
{
    std::mutex m1;

public:
    inline static int i{0};
    void operator()()
    {
        m1.lock();
        ++i;
        m1.unlock();
    }
};


int main()
{
    test t;
    std::thread t1{t}; // doesn't work
    // std::thread t1{std::ref(t)}; // works

    t1.join();

    cout << test::i << endl;
}

Error:

In file included from test.cpp:19:
/Library/Developer/CommandLineTools/usr/include/c++/v1/thread:365:17: error: no matching constructor for initialization of
      '_Gp' (aka 'tuple<unique_ptr<std::__1::__thread_struct>, test>')
            new _Gp(std::move(__tsp),
                ^   ~~~~~~~~~~~~~~~~~
test.cpp:53:17: note: in instantiation of function template specialization 'std::__1::thread::thread<test &, void>' requested
      here
    std::thread t1{t}; // doesn't work
                ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:625:5: note: candidate template ignored: requirement
      '__lazy_and<is_same<allocator_arg_t, unique_ptr<__thread_struct, default_delete<__thread_struct> > >,
      __lazy_all<__dependent_type<is_default_constructible<unique_ptr<__thread_struct, default_delete<__thread_struct> > >,
      true>, __dependent_type<is_default_constructible<test>, true> > >::value' was not satisfied [with _AllocArgT =
      std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, _Alloc = test,
      _Dummy = true]
    tuple(_AllocArgT, _Alloc const& __a)
    ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:641:5: note: candidate template ignored: requirement
      '_CheckArgsConstructor<true>::template __enable_implicit<const std::__1::unique_ptr<std::__1::__thread_struct,
      std::__1::default_delete<std::__1::__thread_struct> > &, const test &>()' was not satisfied [with _Dummy = true]
    tuple(const _Tp& ... __t) _NOEXCEPT_((__all<is_nothrow_copy_constructible<_Tp>::value...>::value))
    ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:659:14: note: candidate template ignored: requirement
      '_CheckArgsConstructor<true>::template __enable_explicit<const std::__1::unique_ptr<std::__1::__thread_struct,
      std::__1::default_delete<std::__1::__thread_struct> > &, const test &>()' was not satisfied [with _Dummy = true]
    explicit tuple(const _Tp& ... __t) _NOEXCEPT_((__all<is_nothrow_copy_constructible<_Tp>::value...>::value))
             ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:723:9: note: candidate template ignored: requirement
      '_CheckArgsConstructor<sizeof...(_Up) == sizeof...(_Tp) && !false>::template
      __enable_implicit<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >,
      test>() || _CheckArgsConstructor<_EnableImplicitReducedArityExtension && sizeof...(_Up) < sizeof...(_Tp) &&
      !false>::template __enable_implicit<std::__1::unique_ptr<std::__1::__thread_struct,
      std::__1::default_delete<std::__1::__thread_struct> >, test>()' was not satisfied [with _Up =
      <std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, test>,
      _PackIsTuple = false]
        tuple(_Up&&... __u)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:756:9: note: candidate template ignored: requirement
      '_CheckArgsConstructor<sizeof...(_Up) <= sizeof...(_Tp) && !_PackExpandsToThisTuple<unique_ptr<__thread_struct,
      default_delete<__thread_struct> >, test>::value>::template
      __enable_explicit<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >,
      test>() || _CheckArgsConstructor<!_EnableImplicitReducedArityExtension && sizeof...(_Up) < sizeof...(_Tp) &&
      !_PackExpandsToThisTuple<unique_ptr<__thread_struct, default_delete<__thread_struct> >, test>::value>::template
      __enable_implicit<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >,
      test>()' was not satisfied [with _Up = <std::__1::unique_ptr<std::__1::__thread_struct,
      std::__1::default_delete<std::__1::__thread_struct> >, test>]
        tuple(_Up&&... __u)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:783:9: note: candidate template ignored: requirement
      '_CheckArgsConstructor<sizeof...(_Up) == sizeof...(_Tp) && !_PackExpandsToThisTuple<>::value>::template
      __enable_implicit<>()' was not satisfied [with _Alloc = test, _Up = <>]
        tuple(allocator_arg_t, const _Alloc& __a, _Up&&... __u)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:803:9: note: candidate template ignored: requirement
      '_CheckArgsConstructor<sizeof...(_Up) == sizeof...(_Tp) && !_PackExpandsToThisTuple<>::value>::template
      __enable_explicit<>()' was not satisfied [with _Alloc = test, _Up = <>]
        tuple(allocator_arg_t, const _Alloc& __a, _Up&&... __u)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:612:23: note: candidate constructor template not viable: requires
      0 arguments, but 2 were provided
    _LIBCPP_CONSTEXPR tuple()
                      ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:677:7: note: candidate constructor template not viable: requires 4
      arguments, but 2 were provided
      tuple(allocator_arg_t, const _Alloc& __a, const _Tp& ... __t)
      ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:697:7: note: candidate constructor template not viable: requires 4
      arguments, but 2 were provided
      tuple(allocator_arg_t, const _Alloc& __a, const _Tp& ... __t)
      ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:822:9: note: candidate constructor template not viable: requires
      single argument '__t', but 2 arguments were provided
        tuple(_Tuple&& __t) _NOEXCEPT_((is_nothrow_constructible<_BaseT, _Tuple>::value))
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:837:9: note: candidate constructor template not viable: requires
      single argument '__t', but 2 arguments were provided
        tuple(_Tuple&& __t) _NOEXCEPT_((is_nothrow_constructible<_BaseT, _Tuple>::value))
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:850:9: note: candidate constructor template not viable: requires 3
      arguments, but 2 were provided
        tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:864:9: note: candidate constructor template not viable: requires 3
      arguments, but 2 were provided
        tuple(allocator_arg_t, const _Alloc& __a, _Tuple&& __t)
        ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/tuple:615:5: note: candidate constructor not viable: requires 1
      argument, but 2 were provided
    tuple(tuple const&) = default;
    ^
In file included from test.cpp:1:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/iostream:38:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/ios:216:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/__locale:15:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/string:500:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/string_view:176:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/__string:56:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/algorithm:640:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/initializer_list:47:
In file included from /Library/Developer/CommandLineTools/usr/include/c++/v1/cstddef:110:
/Library/Developer/CommandLineTools/usr/include/c++/v1/type_traits:2360:12: error: call to implicitly-deleted copy constructor
      of 'typename decay<test &>::type' (aka 'test')
    return _VSTD::forward<_Tp>(__t);
           ^~~~~~~~~~~~~~~~~~~~~~~~
/Library/Developer/CommandLineTools/usr/include/c++/v1/__config:508:15: note: expanded from macro '_VSTD'
#define _VSTD std::_LIBCPP_NAMESPACE
              ^
/Library/Developer/CommandLineTools/usr/include/c++/v1/thread:366:21: note: in instantiation of function template
      specialization 'std::__1::__decay_copy<test &>' requested here
                    __decay_copy(_VSTD::forward<_Fp>(__f)),
                    ^
test.cpp:53:17: note: in instantiation of function template specialization 'std::__1::thread::thread<test &, void>' requested
      here
    std::thread t1{t}; // doesn't work
                ^
test.cpp:36:16: note: copy constructor of 'test' is implicitly deleted because field 'm1' has an inaccessible copy constructor
    std::mutex m1;
               ^
2 errors generated.

This program fails to compile when I pass in the functor into the thread. But it works when I wrap it with std::ref. It appears the class member mutex is the issue, but I am not sure why. Can someone explain why the std::ref wrapper allows this to compile but without it, the program doesn't compile?

The compiler error message doesn't seem to help.

user5965026
  • 465
  • 5
  • 16
  • 2
    Think on how bad an idea copy-able `mutex`s would be. – user4581301 Aug 16 '20 at 05:35
  • @user4581301 This is my first time using functors for threads. I typically use function pointers. When you pass in a function object, is it actually copying the function object into the thread? I wasn't aware of that. – user5965026 Aug 16 '20 at 05:37
  • @user5965026 When you use function pointers it's copying the function pointer into the thread. Copying is the default in C++. – john Aug 16 '20 at 05:38
  • 1
    @john Oh I see (I was under the (wrong) assumption that somehow threads behaved differently than the C++ norm). So in this case when the functor is passed in, a copy of it is made, which means the mutex is copied. Why is this an issue? – user5965026 Aug 16 '20 at 05:39
  • 1
    The default behaviour of threads are to make copies. Among other things, this prevents the general nastiness of having to synchronize the data across threads and the data going out of scope before the thread completes. – user4581301 Aug 16 '20 at 05:39
  • You always should check the documentation about what constructors a class has, and what comments are provided with them. [std::thread](https://en.cppreference.com/w/cpp/thread/thread/thread) – t.niese Aug 16 '20 at 05:40
  • @user5965026 because `mutex` is not copyable. That what this line in your error message is about `note: copy constructor of 'test' is implicitly deleted because field 'm1' has an inaccessible copy constructor std::mutex m1;` – t.niese Aug 16 '20 at 05:41
  • 1
    A `mutex` keeps multiple threads out of the same critical section of code. If different threads have different copies of the `mutex`, they can all lock their copy, enter the critical section, and render the `mutex` useless. – user4581301 Aug 16 '20 at 05:42
  • Side note: Prefer one of the [RAII](https://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii)-managed [mutex lockers](https://stackoverflow.com/questions/43019598/stdlock-guard-or-stdscoped-lock). When the locking object goes out of scope for any reason, it unlocks. This ensures the `mutex` is unlocked in the event of an unexpected return or thrown exception. – user4581301 Aug 16 '20 at 05:46
  • @user4581301 Right, I understand that. But if the functor is being copied, then isn't that particular thread working on its own distinct critical section? – user5965026 Aug 16 '20 at 05:46
  • Another possible work around is by using a [lambda](https://en.cppreference.com/w/cpp/language/lambda) which captures the object `t` *by reference*, as in `std::thread t1([&t](){ t(); });` – Some programmer dude Aug 16 '20 at 05:49
  • 1
    @Someprogrammerdude wouldn't that have the same effect as using `std::ref(t)`? – t.niese Aug 16 '20 at 05:50
  • Yes. But multiple entry to a critical section is a bad thing. In the case of the shared `i` variable, 10 threads could all hit it at the same time and wreck havoc – user4581301 Aug 16 '20 at 05:50
  • @Someprogrammerdude What's the benefit of that over `std::ref`? – user5965026 Aug 16 '20 at 05:50
  • @user4581301 Yeah, it's a bad thing. I guess I thought this would be one of those bad things that wouldn't be picked up by a compiler, and would be something that the programmer has to deal with. – user5965026 Aug 16 '20 at 05:51
  • 1
    Here a link to a question about [Why is std::mutex neither copyable nor movable?](https://stackoverflow.com/questions/62369119). – t.niese Aug 16 '20 at 05:57
  • @t.niese Just to confirm. In the cppreference link you sent earlier, both the copy and reference passing is invoking constructor (3) right? This one seems to take in an r-value reference. – user5965026 Aug 16 '20 at 06:04
  • @user5965026 yes its `(3)` the important part there is the information about the `decay_copy` – t.niese Aug 16 '20 at 06:26

1 Answers1

2

The default behaviour of threads is to make copies. Among other things, this prevents the general nastiness of having to synchronize the data across threads and the data going out of scope before the thread completes.

In this specific case, the object being copied contains a mutex, and mutexs cannot be copied or moved. A mutex keeps multiple threads out of the same critical section of code. If different threads have different copies of the mutex, they can all lock their copy and enter the critical section, rendering the mutex useless. They must all share the same mutex, and in this case this means all threads must all share the same test.

In this case a static mutex m1 would be a viable solution in addition to passing by reference.

Note: Because i is public and accessible to anyone, i can easily be accessed by anyone regardless of the mutex.

Note: Prefer to use a std::lock_guard or a std::scoped_lock instead of manually calling lock and unlock manually. A lock_guard or scoped_lock locks the mutex on construction and unlocks on destruction, guaranteeing the mutex will be unlocked when the locking object goes out of scope.

Note: Removing the mutexand using std::atomic<int> in place of int should solve most synchronization problems.

user4581301
  • 33,082
  • 7
  • 33
  • 54