0

Consider the following code:

#include <thread>

struct S {
    S() {
        std::thread t(&S::bar, *this);
        t.join();
    }

    void bar() {
        // Do stuff...
    }
};

int main() {
    S s;
}

Is this code legal? Note that we call a member function on *this, although the constructor has not yet run to the end. Does the situation change if we make bar virtual?

virtual void bar() {

Is the behaviour of bar in this case the same as here?

Please note that I only care if the standard defines this, I know that it's most likely considered bad practice by most.

Brotcrunsher
  • 1,964
  • 10
  • 32
  • That the call happens in another thread should not matter, it shouldn't be different from calling `bar` directly in the constructor. – Some programmer dude Sep 16 '21 at 17:24
  • 1
    Legal, yes. All members are constructed by the time you enter the body of the constructor. That doesn't mean that you won't run into problems if the called function makes use of member that is not valid after construction, for example two phase construction that hasn't finished phase 2 yet or a fundamental type that is still uninitialized. – user4581301 Sep 16 '21 at 17:25
  • 1
    The question is not thread-specific. There are certain interesting edge cases (i.e. virtual functions called from constructors are not called virtually), but example provided is semantically equivalent with calling `bar` directly from constructor body. – SergeyA Sep 16 '21 at 17:28
  • Nothing I can add on the `virtual` case that isn't covered in the [here](https://stackoverflow.com/questions/962132/calling-virtual-functions-inside-constructors) link. If you have specific questions about stuff you don't understand in the answers to the linked question, you should call them out specifically in another question. – user4581301 Sep 16 '21 at 17:32
  • @user4581301 I do understand the answers from the link. However, I did not know that this seems to still be considered a call to `bar` from the constructor. I thought that the switch to another thread would be considred a completely different place and thus maybe different rules would apply. According to the comments so far this is not the case though. – Brotcrunsher Sep 16 '21 at 17:35
  • 2
    @Brotcrunsher It is only identical to calling `bar` because you `join` immediately after the thread creation, which synchronize the makes the entire approach single-threaded. Anywhere it might matter, only one thread is running at a time. – François Andrieux Sep 16 '21 at 17:42
  • It isn't the same as a call to bar from the constructor, but it will have the same effect. Remove the `join` and the constructor could still pause immediately as the system allocates the CPU to the thread running `bar` and then resume when `bar` exits and the thread terminates. With multiple CPUs and no `join` the two could run simultaneously, and that could open up a whole new can of worms if the body of the constructor does more than start the thread. – user4581301 Sep 16 '21 at 17:47

1 Answers1

0

Technically, yes. But ill advised to be sure. What if another class derives from S and makes changes? It would be easy to get into data racing.

And the case with virtual class case is UB for the most cases. If S::bar is virtual and you create a class D that derives from S then it is 100% UB.

You see, when S is constructed - D hasn't been constructed yet and the virtual table reference hasn't been assigned to the instance. Even just calling bar() in constructors is deemed "never do" in most C++ guides as it will not call the final virtual function and if bar() wasn't implemented in S it will cause errors.

So by creating a thread you'll get data race in the call std::thread t(&S::bar, *this); which is 100% UB as pointer to bar (or rather to the vtable) will change in the next couple of moments.

ALX23z
  • 4,456
  • 1
  • 11
  • 18
  • Can you explain where the data race comes from when we immediately call `.join()`? – Brotcrunsher Sep 16 '21 at 17:38
  • @Brotcrunsher oh, my bad didn't notice the `join()`. Why the hell do you create a threat and immediately call join()? It doesn't make any sense - just call the function instead. You still call the `S::bar` instead of the `D::bar` as vtable isn't updated. – ALX23z Sep 16 '21 at 17:40
  • I wanted to provide a minimal code example. In practice probably several such threads would be created at the same time or all this would instead be sent to another thread and then waited for it to be finished. – Brotcrunsher Sep 16 '21 at 17:42
  • @Brotcrunsher it can work but it is ill advised to create threads in constructor and wait on their completion. At least move it to some `Init()` function - and even then you should consider whether it is worthwhile to offload all of the stuff to other threads - thread creation is not too cheap and mindlessly launching multiple thread is unlikely to improve performance. Besides, overcomplicating constructors is unhealthy when dealing with inheritance and virtual functions. – ALX23z Sep 16 '21 at 17:44