5

I'm trying to figure out threads after using them a bit in Java and I'm a bit baffled. Two questions:

  • Can I extend my class from a thread or must I manage the thread from within the class via a handler?
  • How do I save said thread handler? std::thread in and of itself does not appear to name a type.

Any prod in the right direction would be greatly appreciated.

How do I interpret this message?

src/CHandler.h:27:9: error: 'thread' in namespace 'std' does not name a type
         std::thread _thread;
         ^

And here's my attempt at extending thread:

src/CHandler.h:17:30: error: expected class-name before '{' token
 class CHandler : std::thread {
                              ^

Complete, troublesome header:

#ifndef __projectm__CHandler__
#define __projectm__CHandler__

#include <set>
#include <vector>
#include <thread>

#include "CListener.h"

class CHandler {
    public:
        virtual bool subscribe(std::shared_ptr<CListener> aListener);
        virtual bool unsubscribe(std::shared_ptr<CListener> aListener);

        virtual bool hasSubscriber(std::shared_ptr<CListener> aListener);

        virtual ~CHandler() {}

    protected:
        std::thread _thread;
        std::vector<std::weak_ptr<CListener> > _subscribers;
        std::set<const CListener *> _subscribersSet;

        virtual void run();
};

#endif /* defined(__projectm__CDefaultHandler__) */

Compiler version:

bash-3.1$ g++ --version
g++.exe (GCC) 4.8.1

The makefile (a mess, I know - still learning this bloody thing):

CC=g++

OUTFILE=game

BINDIR=bin
SRCDIR=src
OBJDIR=obj

CFLAGS=
LDFLAGS=-std=c++0x



all: core

# Ядро проекта.
core: $(OBJDIR)/main.o $(OBJDIR)/CGame.o $(OBJDIR)/CHandler.o $(OBJDIR)/CListener.o
    $(CC) $(CFLAGS) $(wildcard $(OBJDIR)/*.o) -o $(BINDIR)/$(OUTFILE)

$(OBJDIR)/main.o: $(OBJDIR)
    $(CC) $(LDFLAGS) $(SRCDIR)/main.cpp -c -o $(OBJDIR)/main.o

$(OBJDIR)/CGame.o: $(OBJDIR)
    $(CC) $(LDFLAGS) $(SRCDIR)/CGame.cpp -c -o $(OBJDIR)/CGame.o

$(OBJDIR)/CHandler.o: $(OBJDIR)
    $(CC) $(LDFLAGS) $(SRCDIR)/CHandler.cpp -c -o $(OBJDIR)/CHandler.o

$(OBJDIR)/CListener.o: $(OBJDIR)
    $(CC) $(LDFLAGS) $(SRCDIR)/CListener.cpp -c -o $(OBJDIR)/CListener.o

# Создаем директорию для объектов, если ее нет.
$(OBJDIR):
    mkdir $(OBJDIR)

main.o: $(SRC)/main.cpp
Maxim Kumpan
  • 2,545
  • 2
  • 19
  • 23
  • 3
    `std::thread` is a class. I don't understand your problem. – Björn Pollex Oct 17 '13 at 09:21
  • @BjörnPollex, sorry, I meant to say "does not name a type". Trying to store a thread object in a variable did not work. – Maxim Kumpan Oct 17 '13 at 09:22
  • It is a type. A class is a type. – juanchopanza Oct 17 '13 at 09:22
  • @juanchopanza, I figured, yet the compiler still throws the not a type error. – Maxim Kumpan Oct 17 '13 at 09:24
  • You should post complete code. You probably forgot to include the appropriate header. – Björn Pollex Oct 17 '13 at 09:25
  • @MaximKumpan: This does not mean that `thread` is not a type in general, just that it is not a type *in your context*. Presumably you forgot to `#include ` and thus `std::thread` is unknown to the compiler (and the error message is confusing). – Matthieu M. Oct 17 '13 at 09:26
  • And yeah, there was no `#include `. Alas, it still throws the same errors when it was added. – Maxim Kumpan Oct 17 '13 at 09:27
  • I'm not sure what the source of the error is, but it is unlikely you need to inherit from thread. Just using a thread data member is usually the better solution. – juanchopanza Oct 17 '13 at 09:28
  • @juanchopanza, I'm still getting `error: 'thread' in namespace 'std' does not name a type` whether I inherit or not. – Maxim Kumpan Oct 17 '13 at 09:30
  • 1
    This isn't the problem, but names that contain two consecutive underscores (`__projectm__CHandler__`) and names that begin with an underscore followed by a capital letter are reserved to the implementation. Don't use them. – Pete Becker Oct 17 '13 at 13:03
  • @PeteBecker, thanks for the tip. That was part of my partner's commit. I'll talk to him about it. – Maxim Kumpan Oct 17 '13 at 13:30

5 Answers5

10

One of the problems with using std::thread as an unadorned local variable is that it is not exception safe. I will admit that I am often guilty of this myself when demonstrating small little HelloWorlds.

However it is good to know exactly what you're getting into, so here is a more detailed explanation of the exception safety aspects of using std::thread:

#include <iostream>
#include <thread>

void f() {}
void g() {throw 1;}

int
main()
{
    try
    {
        std::thread t1{f};
        g();
        t1.join();
    }
    catch (...)
    {
        std::cout << "unexpected exception caught\n";
    }
}

In the above example, I have a "large" program, which "occasionally" throws an exception. Typically I want to catch and handle exceptions before they bubble up to main. However as a last resort, main itself is wrapped up in a try-catch-all. In this example I simply print out that something really bad has happened and quit. In a more realistic example you might give your client a chance to save work, or free up memory or disk space, launch a different process that files a bug report, etc.

Looks good, right? Unfortunately wrong. When you run this, the output is:

libc++abi.dylib: terminating
Abort trap: 6

I didn't give my client the notification that something went wrong before returning from main normally. I was expecting this output:

unexpected exception caught

Instead std::terminate() got called.

Why?

As it turns out, ~thread() looks like this:

thread::~thread()
{
    if (joinable())
        terminate();
}

So when g() throws, t1.~thread() runs during stack unwinding, and without t1.join() getting called. Thus t1.~thread() calls std::terminate().

Don't ask me why. It is a long story, and I lack the objectivity to tell it unbiasedly.

Regardless, you have to know about this behavior, and guard against it.

One possible solution is to go back to the wrapper design, perhaps using private inheritance as first proposed by the OP and warned against in other answers:

class CHandler
    : private std::thread
{
public:
    using std::thread::thread;
    CHandler() = default;
    CHandler(CHandler&&) = default;
    CHandler& operator=(CHandler&&) = default;
    ~CHandler()
    {
        if (joinable())
            join();  // or detach() if you prefer
    }
    CHandler(std::thread t) : std::thread(std::move(t)) {}

    using std::thread::join;
    using std::thread::detach;
    using std::thread::joinable;
    using std::thread::get_id;
    using std::thread::hardware_concurrency;

    void swap(CHandler& x) {std::thread::swap(x);}
};

inline void swap(CHandler& x, CHandler& y) {x.swap(y);}

The intent is to create a new type, say CHandler that behaves just like a std::thread, except for its destructor. ~CHandler() should call either join() or detach() in its destructor. I've chosen join() above. Now one can simply substitute CHandler for std::thread in my example code:

int
main()
{
    try
    {
        CHandler t1{f};
        g();
        t1.join();
    }
    catch (...)
    {
        std::cout << "unexpected exception caught\n";
    }
}

and the output is now:

unexpected exception caught

as intended.

Why choose join() instead of detach() in ~CHandler()?

If you use join(), then the stack unwinding of the main thread will block until f() completes. This may be what you want, or it may not be. I can not answer this question for you. Only you can decide this design issue for your application. Consider:

// simulate a long running thread
void f() {std::this_thread::sleep_for(std::chrono::minutes(10));}

The main() thread will still throw an exception under g(), but now it will hang during unwinding, and only 10 minutes later print out:

unexpected exception caught

and exit. Perhaps because of references or resources that are used within f(), this is what you need to have happen. But if it is not, then you can instead:

    ~CHandler()
    {
        if (joinable())
            detach();
    }

and then your program will immediately output "unexpected exception caught" and return, even though f() is still busy crunching away (after main() returns f() will be forcefully canceled as part of a normal shutdown of the application).

Perhaps you need join()-on-unwinding for some of your threads and detach()-on-unwinding for others. Perhaps this leads you to two CHandler-like wrappers, or to a policy-based wrapper. The committee was unable to form a consensus for a solution, and so you must decide what is right for you, or live with terminate().

This makes direct use of std::thread very, very low-level behavior. Ok for Hello World, but in a real application, best encapsulated away in a mid-level handler, either via private inheritance or as a private data member. The good news is that in C++11 that mid-level handler can now be written portably (on top of std::thread) instead of writing down to the OS or a 3rd-party lib as is necessary in C++98/03.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
9

The recommendation is not to inherit from std::thread: it has no virtual method anyway. I would even recommend not to use composition.

The main issue with std::thread is that it will start a thread as soon as it is built (except if you use its default constructor). Therefore, a number of situations are fraught with peril:

// BAD: Inheritance
class Derived: std::thread {
public:
    Derived(): std::thread(&Derived::go, this), _message("Hello, World!") {}

    void go() const { std::cout << _message << std::endl; }

private:
    std::string _message;
};

The thread may execute go before _message is built, leading to a data race.

// BAD: First Attribute
class FirstAttribute {
public:
    FirstAttribute(): _thread(&Derived::go, this), _message("Hello, World!") {}

    void go() const { std::cout << _message << std::endl; }

private:
    std::thread _thread;
    std::string _message;
};

Same issue, the thread may execute go before _message is built, leading to a data race.

// BAD: Composition
class Safer {
public:
    virtual void go() const = 0;

protected:
    Safer(): _thread(&Derived::go, this) {}

private:
    std::thread _thread;
};

class Derived: Safer {
    virtual void go() const { std::cout << "Hello, World!\n"; }
};

Same issue, the thread may execute go before Derived is built, leading to a data race.


As you can see, whether you inherit or compose, it is very easy to unwittingly cause a data race. Using std::thread as the last attribute of a class would work... if you can ensure nobody derives from this class.

It thus seems better to me, for now, to recommend only using std::thread as local variables. Note that if you use the async facility, you won't even have to manage a std::thread by yourself.

James C
  • 366
  • 4
  • 6
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
6

Bjarne Stroustrup shows some examples of using std::thread in his C++11 FAQ. The simplest example looks like this:

#include<thread>

void f();

struct F {
    void operator()();
};

int main()
{
    std::thread t1{f};  // f() executes in separate thread
    std::thread t2{F()};    // F()() executes in separate thread
}

In general, std::thread is not intended to be inherited from. You pass a function to execute asynchronously in the constructor.

If your compiler does not support std::thread, you could use Boost.Thread instead. It is fairly compatible, so you can replace it by std::thread once your compiler supports it.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • This looks well and good, but declaring `std::thread _thread;` returns `error: 'thread' in namespace 'std' does not name a type` – Maxim Kumpan Oct 17 '13 at 09:37
  • This likely means that you have some include screwed up. It is impossible to diagnose the error without full source code. Have you tried compiling the example I have posted? If it works, then you know its not your compiler. You should probably do some reading on how includes work in C++, as they are quite different from package imports in Java. – Björn Pollex Oct 17 '13 at 09:39
  • Good question. I'll try compiling that example and see if thread works in and of itself. – Maxim Kumpan Oct 17 '13 at 09:46
  • No, it does not work. `g++ -std=c++0x -o test test.cpp \\ test.cpp: In function 'int main()': \\ test.cpp:11:5: error: 'thread' is not a member of 'std'` – Maxim Kumpan Oct 17 '13 at 09:54
  • @MaximKumpan: I have updated my answer. As a workaround, you can use Boost. – Björn Pollex Oct 17 '13 at 09:58
  • 1
    Nothing is more permanent than a temporary solution. But I don't think Boost will be a problem as a legacy threading solution. I'll give it a try. Thanks for the help. – Maxim Kumpan Oct 17 '13 at 10:01
0

First of all what compiler and compiler version are you using? std::thread is fairly new and wasn't implemented in some of them until very recently. That could be your problem.

Secondly did you

#include <thread> 

Thirdly (and this isn't your immediate problem) that's not how to use thread in c++. You don't inherit from it, you create an instance passing in the function you want it to run.

std::thread mythread = std::thread(my_func);

(You can pass in a lot more than a simple function though)

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
jcoder
  • 29,554
  • 19
  • 87
  • 130
  • I'm using g++ from the mingw package via custom makefile. `LDFLAGS=-std=c++0x`. Include thread did not help. Simply declaring `std::thread mythread` still throws an error. – Maxim Kumpan Oct 17 '13 at 09:29
  • @MaximKumpan which version of g++ do you use? – PeterT Oct 17 '13 at 09:30
  • @PeterT, added g++ version to question. – Maxim Kumpan Oct 17 '13 at 09:32
  • No need for the funny syntax: `std::thread mythread(my_func);` will do. – juanchopanza Oct 17 '13 at 09:32
  • 2
    This is the problem. mingw does not support std::thread on windows at this time as far as I know. It certainly didn't recently. The current version of visual c++ on windows does. – jcoder Oct 17 '13 at 09:34
  • 1
    Not answer to the question but some comments on that here http://programmers.stackexchange.com/questions/195639/is-gcc-dying-without-threads-support-on-windows – jcoder Oct 17 '13 at 09:35
  • @jcoder, that sounds a fair bit more like the answer. I'll do some research. Meanwhile, if you're sure of it, post that as an answer. – Maxim Kumpan Oct 17 '13 at 09:39
  • @MaximKumpan btw. there seem to [other versions](http://stackoverflow.com/questions/15718904/mingw-and-stdthread) of minigw that actually have working std::thread support like minigw-w64 – PeterT Oct 17 '13 at 09:46
0

Make sure when you compile and link, you use:

g++ -std=c++11 your_file.cpp -o your_program 

messing around with LDFLAGS will only help linking, not compiling.

doron
  • 27,972
  • 12
  • 65
  • 103