How to use this pointer in class
TOC
Usages
- How to use this pointer as an argument of callback function
- How to use this pointer as a static member variable.
- How to make a library with this pointer
Explain
Callback with this argument
Generally, OS provides a C API to control threads. Code 1 shows a simple traditional C++ example (C++98/03) to create a thread with a callback function in Linux.
// 01_C_testThread.cpp
#include <iostream>
#include <pthread.h>
using namespace std;
void* foo(void *p)
{
string* str = static_cast<string*>(p);
cout << str->c_str() << endl;
return NULL;
}
int main() {
pthread_t tid;
string str = "Thread Start";
// Create thread
pthread_create(&tid, NULL, foo, static_cast<void*>(&str));
// Wait for thread
pthread_join(tid, NULL);
cout << "Thread id: " << tid << endl;
return 0;
}
// Compile: clang++ -pthread
// -o 01_C_testThread 01_C_testThread.cpp
Thread Start
Thread id: 140625819205632
When Code 1 is executed, pthread_create(), which is a C API, creates a thread. Then, the thread executes foo() to print "Thread start". It works fine, but it is not easy to use pthread_create() for a user. The user should know what the all arguments are, even though the user does not need special handling for a thread. If there is a library for thread and it wraps pthread_crate () with single exposed argument for callback, the user can easily create thread.
In this case, it is important to use a static member function as a callback. Because pthread_create() is a C API and it does not know which instance is a target, it can only call a static member function. Fortunately, The fourth argument of pthread_create() is an argument for the callback, so it is possible to pass this pointer of an instance. With this pointer, the callback can specify which instnace is a target. Code 2 shows how to make a thread library and use it.
// 02_C_testThreadInClass.cpp
#include <iostream>
#include <pthread.h>
using namespace std;
// Library
class ThreadLib
{
private:
pthread_t tid;
public:
ThreadLib()
: tid(0)
{}
void run()
{
// Create thread
pthread_create(&tid, NULL, _threadLoop, this);
// Wait for thread
pthread_join(tid, NULL);
cout << "Thread id: " << tid << endl;
return;
}
static void* _threadLoop(void* p)
{
ThreadLib* const self = static_cast<ThreadLib*>(p);
self->threadLoop(); // threadLoop(self)
return NULL;
}
virtual bool threadLoop()
{
cout << "ThreadLib's threadLoop" << endl;
return false;
}
};
// User defined library
class MyThreadLib : public ThreadLib
{
virtual bool threadLoop()
{
cout << "MyThreadLib's threadLoop" << endl;
return true;
}
};
int main()
{
MyThreadLib t;
t.run(); // Thread is created and
// threadLoop() of MyThreadLib is executed
return 0;
}
// Compile: clang++ -pthread
// -o 02_C_testThreadInClass 02_C_testThreadInClass.cpp
MyThreadLib's threadLoop
Thread id: 139854596114176
_threadLoop() is a static member function, and it has argument p for this. P is converted to ThreadLib class pointer, and it calls threadLoop(), which is overrided by an user's class. Therefore, only thing user have to do is to make threadLoop() with a function what he/she wants.
Callback without this argument
How about the callback function having no redundant argument? Code 3 shows a simple example for a callbak whose arguments are reserved.
// 03_C_testTimer.cpp
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
using namespace std;
typedef pair<pthread_t, int> Timer;
void* foo(void *p)
{
// Get timer information
Timer* timer = static_cast<Timer*>(p);
// Wait
sleep(timer->second);
cout << "Thread id: " << timer->first
<< ", time: " << timer->second << endl;
return NULL;
}
Timer* setTimer(int _time, void* (*func)(void *))
{
Timer* timer = new Timer();
timer->second = _time;
// Create thread
pthread_create(&(timer->first), NULL, func, static_cast<void*>(timer));
return timer;
}
typedef vector<Timer *>::iterator TIterator;
void waitTimer(vector<Timer *>& timer)
{
for (TIterator tItr = timer.begin(); tItr != timer.end(); ++tItr)
{
// Wait for thread
pthread_join((*tItr)->first, NULL);
// Clear memory
delete *tItr;
*tItr = NULL;
}
}
int main()
{
vector<Timer*> timers;
// Set timer
timers.push_back(setTimer(10, foo));
timers.push_back(setTimer(5, foo));
waitTimer(timers);
return 0;
}
// Compile: clang++ -pthread
// -o 03_C_testTimer 03_C_testTimer.cpp
Thread id: 140388092651264, time: 5
Thread id: 140388101043968, time: 10
setTimer() sets a timer with a callback function and returns a pointer for a pair of thread id and setting time. WaitTimer() sets a barrier for threads and deletes allocated memory for timer. However, setTimer() does not have an argument to send this pointer. In this case, a container is necessary to hold a pair of an thread id and this pointer for instance. Code 4 shows how setTimer() and waitTimer () are wrapped up in a class.
// 04_C_testTimerForId.cpp
#include <iostream>
#include <map>
#include <vector>
#include <unistd.h>
#include <pthread.h>
using namespace std;
typedef pair<pthread_t, int> Timer;
Timer* setTimer(int _time, void* (*func)(void *))
{
Timer* timer = new Timer();
timer->second = _time;
// Create thread
pthread_create(&(timer->first), NULL, func, static_cast<void*>(timer));
return timer;
}
typedef vector<Timer *>::iterator TIterator;
void waitTimer(vector<Timer *>& timer)
{
for (TIterator tItr = timer.begin(); tItr != timer.end(); ++tItr)
{
// Wait for thread
pthread_join((*tItr)->first, NULL);
// Clear memory
delete *tItr;
*tItr = NULL;
}
}
class TimerLib
{
string name;
vector<Timer *> timers;
static map<int, TimerLib *> this_map;
public:
TimerLib(string s)
: name(s)
{}
void start(int sec)
{
Timer* timer = setTimer(sec, timerHandler);
// Store this pointer to map
this_map[timer->first] = this;
timers.push_back(timer);
}
static void* timerHandler(void* p)
{
Timer* timer = static_cast<Timer *>(p);
// Get this pointer by map
TimerLib* self = this_map[timer->first];
sleep(timer->second);
cout << "Thread id: " << timer->first
<< ", time: " << timer->second
<< ", Name: " << self->name << endl;
return NULL;
}
void wait()
{
waitTimer(timers);
}
};
// A static member variable should be declared
// globally again
map<int, TimerLib*> TimerLib::this_map;
int main()
{
TimerLib c1("AAA");
TimerLib c2("BBB");
// Start timer
c1.start(10);
c2.start(5);
// Wait timer
c1.wait();
c2.wait();
return 0;
}
// Compile: clang++ -pthread
// -o 04_C_testTimerForId 04_C_testTimerForId.cpp
Thread id: 139971167282944, time: 5, Name: BBB
Thread id: 139971175675648, time: 10, Name: AAA
Start() sets a timer and stores thread id and this pointer in a map container. After that, when timerHandler() is called, it finds this pointer of the target instance and does something with the pointer. Therefore, this is not necessary to be sent a callback function, if it is stored in a static container.
Library with a callback not having this argument
Sometimes, a callback function has conditional behaviors. Code 5 is the example for that case.
// 05_C_testCondition.cpp
#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
using namespace std;
typedef pair<pthread_t, int> Timer;
Timer* setTimer(int _time, void* (*func)(void *))
{
Timer* timer = new Timer();
timer->second = _time;
// Create thread
pthread_create(&(timer->first), NULL, func, static_cast<void*>(timer));
return timer;
}
typedef vector<Timer *>::iterator TIterator;
void waitTimer(vector<Timer *>& timer)
{
for (TIterator tItr = timer.begin(); tItr != timer.end(); ++tItr)
{
// Wait for thread
pthread_join((*tItr)->first, NULL);
// Clear memory
delete *tItr;
*tItr = NULL;
}
}
void* foo(void* p)
{
Timer* timer = static_cast<Timer*>(p);
int time = timer->second;
sleep(time);
// Print message
switch (time)
{
case 10:
cout << "Interval 10" << endl;
break;
case 5:
cout << "Interval 5" << endl;
break;
default:
cout << "Wrong time interval" << endl;
}
return NULL;
}
int main()
{
vector<Timer*> timers;
timers.push_back(setTimer(10, foo));
timers.push_back(setTimer(5, foo));
waitTimer(timers);
return 0;
}
// Compile: clang++ -pthread
// -o 05_C_testCondition 05_C_testCondition.cpp
Interval 5
Interval 10
In this example, according to a time interval, print messages are different. If the behaviors are different according to the class type, not only time interval, how could we do that?
To do that, function overriding is useful. The functions for behaviors are redefined in user class. Code 6 shows base and user library for that case and how to use it.
// 06_C_testConditionOverride.cpp
#include <iostream>
#include <vector>
#include <map>
#include <unistd.h>
#include <pthread.h>
using namespace std;
typedef pair<pthread_t, int> Timer;
Timer* setTimer(int _time, void* (*func)(void *))
{
Timer* timer = new Timer();
timer->second = _time;
// Create thread
pthread_create(&(timer->first), NULL, func, static_cast<void*>(timer));
return timer;
}
typedef vector<Timer *>::iterator TIterator;
void waitTimer(vector<Timer *>& timer)
{
for (TIterator tItr = timer.begin(); tItr != timer.end(); ++tItr)
{
// Wait for thread
pthread_join((*tItr)->first, NULL);
// Clear memory
delete *tItr;
*tItr = NULL;
}
}
// Library
class TimerLib
{
vector<Timer *> timers;
// Map for this pointer
static map<int, TimerLib*> this_map;
public:
void start(int sec)
{
Timer* timer = setTimer(sec, foo);
// Store this pointer to map
this_map[timer->first] = this;
timers.push_back(timer);
}
static void* foo(void* p)
{
Timer* timer = static_cast<Timer*>(p);
// Get this pointer from map
pthread_t handle = timer->first;
int time = timer->second;
TimerLib* self = this_map[handle];
sleep(time);
// Call print function
switch (time)
{
case 10:
self->waitSec10();
break;
case 5:
self->waitSec5();
break;
default:
self->wrongSec();
}
return NULL;
}
void wait()
{
waitTimer(timers);
}
virtual void waitSec10() {}
virtual void waitSec5() {}
virtual void wrongSec() {}
};
// User's Class
class MyTimeLib : public TimerLib
{
public:
// Print function
virtual void waitSec10()
{
cout << "Interval 10" << endl;
}
virtual void waitSec5()
{
cout << "Interval 5" << endl;
}
virtual void wrongSec()
{
cout << "Wrong time interval" << endl;
}
};
map<int, TimerLib*> TimerLib::this_map;
int main()
{
MyTimeLib t;
// Start timer
t.start(10);
// Wait for timer
t.wait();
return 0;
}
// Compile: clang++ -pthread
// -o 06_C_testConditionOverride 06_C_testConditionOverride.cpp
Interval 10
Now, a user does not need to know how TimerLib class is implemented, but he/she can makes a behavior function in a user class, and execute it. Foo() is a static member function to be called by pthread_create(). handle is a kind of id, and it is used to find the instance pointer, this.
Summary
- If a callback function has an argument for this pointer, use it.
pthread_create(&tid, NULL, _threadLoop, this);
- If a callback function does not have an argument for this pointer, use map container or any containers.
static map this_map;
// Register
id = setTimer(ms, timerHandler);
this_map[id] = this;
// Execute
TimerLib* self = this_map[id];
cout << self->name << endl;
foo();
- Capsulating a C API is useful to change or extend functionality.
COMMENTS