Threads are seen as scary and strange. If used incorrectly, they can be frustrating and appear inconsistent. The good news is that C++11 has a great threading model and still links with useful libraries so you can get work done.
Minimum to Use std::thread
To use threads, you must know a little bit about how function pointers work in C++, because function pointers are how you specify the work to do in the thread. These function pointers specify what is known as the work function. At a minimum, your program also needs:
[table-wrap bordered=”true” striped=”true”]
What | Why |
---|---|
[cpp inline=”True”]#include [/cpp] | Class declaration |
[cpp inline=”True”]void workFunc();[/cpp] | Declaration of thread work function |
[cpp inline=”True”]std::thread t1(&workFunc);[/cpp] | Thread instance |
[cpp inline=”True”]t1.join();[/cpp] | Wait for thread to finish and clean up |
Starting Threads with “Regular” Functions
Function pointers for “regular” functions, also known as file-scope functions, work pretty much like you’d expect: take the address of the function name and then provide the arguments like you would if calling the function.
[cpp title=”Launch Thread with a ‘Regular’ Function”]
void printHello(const std::string& name) {
std::cout << “Hello, ” << name << “!” << std::endl;
}
std::string name(“Jeff”);
// address of the function name followed by argument value
std::thread t1(&printHello,name); // file-scope (“regular”) function
t1.join();
Note that you can also get by without explicitly taking the address, like [cpp inline=”True”]std::thread t1(printHello,name);[/cpp]. This also works for static member functions, but it does not work with (non-static) member functions. So, for consistency, we will take the addresses of all work functions.
Starting Threads with Static Member Functions
In both the case of a file scope function and a static member function, the function pointer does not depend on a class instance. Given the semantic similarity of file scope functions and static member functions, they (unsurprisingly) use similar syntax when specifying a thread work function.
[cpp title=”Starting a Thread with a Static Member Function”] class Salutations {public:
static void printGoodbye(const std::string& name) {
std::cout << “Goodbye, ” << name << “!” << std::endl;
}
};
std::string name(“Jeff”);
std::thread t3(&Salutations::printGoodbye,name); // static class function
t3.join();
[/cpp]
Launching a Thread with a Member Function
Member functions are fundamentally different from file scope and static member functions because member functions access member data in a particular instance of a class. Member function pointers are a different type from pointers to file scope functions and static member functions. They are usually larger, sometimes much larger. It is not uncommon for a member function pointer to be twice the size of a file scope function pointer. This is because the member function pointer must store both a function pointer and a pointer to the member data associated with the instance.
Whenever you invoke a member function pointer, the first argument must be an instance pointer of the appropriate type.
[cpp title=”Launching a Thread with a Member Function Pointer”]
class Salutations {
const std::string m_name;
public:
Salutations(const std::string& name) : m_name(name) {}
void slang() {
std::cout << “What’s up, ” << m_name << “?” << std::endl;
}
};
Salutations salutations(“Jeff”);
std::thread t2(&Salutations::slang,&salutations); // member function
t2.join();
Putting It All Together
Here, we show the whole program followed by its output. The above fragments do not compile.
[cpp title=”threads_5min.cpp”] //! thread_5min.cpp//! Demonstrate launching native C++11 threads different types of work functions.
//! \author Jeff Benshetler for BranchPoint, Inc.
//! \date 2014-03-26
#include #include
#include
//! File scope function that prints a standard greeting
//! \param name The greeting is customized with the provided name.
//! \returns Nothing
void printHello(const std::string& name) {
std::cout << “Hello, ” << name << “!” << std::endl;
}
//! Class containing both static member function and member function
//! both of which are used as thread work functions.
class Salutations {
const std::string m_name;
public:
//! static member function printing a standard farewell
//! \param name Generated greeting is customized with the provided name.
//! \returns Nothing
static void printGoodbye(const std::string& name) {
std::cout << “Goodbye, ” << name << “!” << std::endl;
}
//! Value constructor
//! \param name Used to customize greetings by member functions
Salutations(const std::string& name) : m_name(name) {}
//! Member function printing slang greeting, customized
//! with the name used to construct the instance.
//! \returns Nothing
void slang() {
std::cout << “What’s up, ” << m_name << “?” << std::endl;
}
}; // end class Salutations
int main(int argc,char* argv[]) {
std::string name(“Jeff”);
Salutations salutations(“Jeff”);
std::thread t1(&printHello,name); // file-scope (“regular”) function
std::thread t2(&Salutations::slang,&salutations); // member function
std::thread t3(&Salutations::printGoodbye,name); // static class function
// In this case, the threads do not interact so there is no particular order
// required to shut them down.
t1.join();
t2.join();
t3.join();
} // end main
[/cpp]
Wrap-up
There is likely less complexity involved than you expected when using threads. Things do get more involved when data structures are shared, but by using RAII along with destructors to handle “atypical” situations, C++11 threads are remarkably usable.