Problem Moving a Thread

I am finally getting a chance to play with std::threads. I want to be able to create a class that
1. Encapsulates the thread's functionality
2. Does not run immediately on construction, but when instructed to do so.

I am able to do what I want with a std::thread pointer...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <unistd.h>
#include <thread>
#include <iostream>

class MyThread
{
public:
	MyThread(int i) : id(i), theThread(nullptr) {}
	void start();
	void waitFor();
	void threadFunction(int i);

private:
	int id;
	std::thread* theThread;
};

void MyThread::start()
{
	theThread = new std::thread(&MyThread::threadFunction, *this, id * 2);
}

void MyThread::waitFor()
{
	if (theThread != nullptr)
	{
		theThread->join();
		delete theThread;
		theThread = nullptr;
	}
}

void MyThread::threadFunction(int i)
{
	for (int j = 0; j < 5; ++j)
	{
		std::cout << i << ": " << j << std::endl;
		sleep(2);
	}
}

int main()
{
	MyThread t(1000);
	std::cout << "Waiting... " << std::endl;
	sleep(4);

	t.start();
	t.waitFor();
}


However, when I tried to do it with a default-constructed std::thread with a move, the following changes did not compile:

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyThread
{
...

private:
	int id;
	std::thread theThread;
};

void MyThread::start()
{
	theThread = std::thread(&MyThread::threadFunction, *this, id * 2);
}


The error I get is

In file included from /usr/include/c++/4.8/functional:55:0,
                 from /usr/include/c++/4.8/thread:39,
                 from main.cpp:2:
/usr/include/c++/4.8/tuple: In instantiation of 'constexpr std::_Head_base<_Idx, _Head, false>::_Head_base(const _Head&) [with long unsigned int _Idx = 1ul; _Head = MyThread]':
/usr/include/c++/4.8/tuple:257:44:   recursively required from 'constexpr std::_Tuple_impl<_Idx, _Head, _Tail ...>::_Tuple_impl(const _Head&, const _Tail& ...) [with long unsigned int _Idx = 1ul; _Head = MyThread; _Tail = {int}]'
/usr/include/c++/4.8/tuple:257:44:   required from 'constexpr std::_Tuple_impl<_Idx, _Head, _Tail ...>::_Tuple_impl(const _Head&, const _Tail& ...) [with long unsigned int _Idx = 0ul; _Head = std::_Mem_fn<void (MyThread::*)(int)>; _Tail = {MyThread, int}]'
/usr/include/c++/4.8/tuple:400:33:   required from 'constexpr std::tuple< <template-parameter-1-1> >::tuple(const _Elements& ...) [with _Elements = {std::_Mem_fn<void (MyThread::*)(int)>, MyThread, int}]'
/usr/include/c++/4.8/functional:1710:74:   required from 'std::_Bind_simple<_Callable(_Args ...)>::_Bind_simple(_Callable&&, _Args2&& ...) [with _Args2 = {MyThread&, int}; <template-parameter-2-2> = void; _Callable = std::_Mem_fn<void (MyThread::*)(int)>; _Args = {MyThread, int}]'
/usr/include/c++/4.8/functional:1759:41:   required from 'typename std::_Bind_simple_helper<_Func, _BoundArgs>::__type std::__bind_simple(_Callable&&, _Args&& ...) [with _Callable = void (MyThread::*)(int); _Args = {MyThread&, int}; typename std::_Bind_simple_helper<_Func, _BoundArgs>::__type = std::_Bind_simple<std::_Mem_fn<void (MyThread::*)(int)>(MyThread, int)>]'
/usr/include/c++/4.8/thread:137:47:   required from 'std::thread::thread(_Callable&&, _Args&& ...) [with _Callable = void (MyThread::*)(int); _Args = {MyThread&, int}]'
main.cpp:20:66:   required from here
/usr/include/c++/4.8/tuple:135:25: error: use of deleted function 'MyThread::MyThread(const MyThread&)'
       : _M_head_impl(__h) { }
                         ^
main.cpp:5:7: note: 'MyThread::MyThread(const MyThread&)' is implicitly deleted because the default definition would be ill-formed:
 class MyThread
       ^
main.cpp:5:7: error: use of deleted function 'std::thread::thread(const std::thread&)'
In file included from main.cpp:2:0:
/usr/include/c++/4.8/thread:126:5: error: declared here
     thread(const thread&) = delete;
     ^


Line 20 refers to the move/assignment statement.

If I change the function to a static and don't pass *this, the move/assignment works.

Why does the compiler think I trying to use the assignment operator rather than the move operator when I try to use a non-static member function?
Last edited on
The thread store copies of the arguments that you pass to the constructor. Since you pass *this to the thread constructor it will try to copy the MyThread object and then call the function on it. When you changed theThread to no longer being a pointer but an actual std::thread object the MyThread class can no longer be copied and that is why you get errors.

If you pass this without using the dereference operator it should work fine.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <unistd.h>
#include <thread>
#include <iostream>

class MyThread
{
public:
	MyThread(int i) : id(i) {}
	void start();
	void waitFor();
	void threadFunction(int i);

private:
	int id;
	std::thread theThread;
};

void MyThread::start()
{
	theThread = std::thread(&MyThread::threadFunction, this, id * 2);
}

void MyThread::waitFor()
{
	if (theThread.joinable())
	{
		theThread.join();
	}
}

void MyThread::threadFunction(int i)
{
	for (int j = 0; j < 5; ++j)
	{
		std::cout << i << ": " << j << std::endl;
		sleep(2);
	}
}

int main()
{
	MyThread t(1000);
	std::cout << "Waiting... " << std::endl;
	sleep(4);

	t.start();
	t.waitFor();
}
Last edited on
@Peter87,

OK. That all makes sense. Thanks for clarifying that for me.

I have a follow-up question. For grins I decided to split the threadFunction out into another class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Other
{
public:
	void threadFunction(int i);
};

class MyThread
{
public:
	MyThread(int i) : id(i) {}
	void start();
	void waitFor();

private:
	int id;
	Other o;
	std::thread theThread;
};



I was able to create the std::thread class with either of the two statements below--both object and pointer.

1
2
3
4
void MyThread::start()
{
	theThread = std::thread(&Other::threadFunction, o, id * 2);
}


1
2
3
4
void MyThread::start()
{
	theThread = std::thread(&Other::threadFunction, &o, id * 2);
}


Can someone explain why the object passed into the thread constructor can be either an object or a pointer?
I guess it's just trying to be clever and do the right thing. It works essentially the same as std::invoke except that invoke does not copy its arguments.

https://en.cppreference.com/w/cpp/utility/functional/invoke
Last edited on
Topic archived. No new replies allowed.