C++ using this pointer in constructors

2019-01-23 23:37发布

In C++, during a class constructor, I started a new thread with this pointer as a parameter which will be used in the thread extensively (say, calling member functions). Is that a bad thing to do? Why and what are the consequences?

My thread start process is at the end of the constructor.

8条回答
太酷不给撩
2楼-- · 2019-01-23 23:55

I'd say that, as a general rule, you should avoid doing this. But you can certainly get away with it in many circumstances. I think there are basically two things that can go wrong:

  1. The new thread might try to access the object before the constructor finishes initializing it. You can work around this by making sure all initialization is complete before you start the thread. But what if someone inherits from your class? You have no control over what their constructor will do.
  2. What happens if your thread fails to start? There isn't really a clean way to handle errors in a constructor. You can throw an exception, but this is perilous since it means that your object's destructor will not get called. If you elect not to throw an exception, then you're stuck writing code in your various methods to check if things were initialized properly.

Generally speaking, if you have complex, error-prone initialization to perform, then it's best to do it in a method rather than the constructor.

查看更多
萌系小妹纸
3楼-- · 2019-01-23 23:57

Basically, what you need is two-phase construction: You want to start your thread only after the object is fully constructed. John Dibling answered a similar (not a duplicate) question yesterday exhaustively discussing two-phase construction. You might want to have a look at it.

Note, however, that this still leaves the problem that the thread might be started before a derived class' constructor is done. (Derived classes' constructors are called after those of their base classes.)

So in the end the safest thing is probably to manually start the thread:

class Thread { 
  public: 
    Thread();
    virtual ~Thread();
    void start();
    // ...
};

class MyThread : public Thread { 
  public:
    MyThread() : Thread() {}
    // ... 
};

void f()
{
  MyThread thrd;
  thrd.start();
  // ...
}
查看更多
在下西门庆
4楼-- · 2019-01-23 23:58

Main consequence is that the thread might start running (and using your pointer) before the constructor has completed, so the object may not be in a defined/usable state. Likewise, depending how the thread is stopped it might continue running after the destructor has started and so the object again may not be in a usable state.

This is especially problematic if your class is a base class, since the derived class constructor won't even start running until after your constructor exits, and the derived class destructor will have completed before yours starts. Also, virtual function calls don't do what you might think before derived classes are constructed and after they're destructed: virtual calls "ignore" classes whose part of the object doesn't exist.

Example:

struct BaseThread {
    MyThread() {
        pthread_create(thread, attr, pthread_fn, static_cast<void*>(this));
    }
    virtual ~MyThread() {
        maybe stop thread somehow, reap it;
    }
    virtual void id() { std::cout << "base\n"; }
};

struct DerivedThread : BaseThread {
    virtual void id() { std::cout << "derived\n"; }
};

void* thread_fn(void* input) {
    (static_cast<BaseThread*>(input))->id();
    return 0;
}

Now if you create a DerivedThread, it's a best a race between the thread that constructs it and the new thread, to determine which version of id() gets called. It could be that something worse can happen, you'd need to look quite closely at your threading API and compiler.

The usual way to not have to worry about this is just to give your thread class a start() function, which the user calls after constructing it.

查看更多
虎瘦雄心在
5楼-- · 2019-01-24 00:03

Some people feel you should not use the this pointer in a constructor because the object is not fully formed yet. However you can use this in the constructor (in the{body} and even in the initialization list) if you are careful.

Here is something that always works: the {body} of a constructor (or a function called from the constructor) can reliably access the data members declared in a base class and/or the data members declared in the constructor's own class. This is because all those data members are guaranteed to have been fully constructed by the time the constructor's {body} starts executing.

Here is something that never works: the {body} of a constructor (or a function called from the constructor) cannot get down to a derived class by calling a virtualmember function that is overridden in the derived class. If your goal was to get to the overridden function in the derived class, you won't get what you want. Note that you won't get to the override in the derived class independent of how you call the virtual member function: explicitly using the this pointer (e.g., this->method()), implicitly using the this pointer (e.g., method()), or even calling some other function that calls the virtual member function on your this object. The bottom line is this: even if the caller is constructing an object of a derived class, during the constructor of the base class, your object is not yet of that derived class. You have been warned.

Here is something that sometimes works: if you pass any of the data members in this object to another data member's initializer, you must make sure that the other data member has already been initialized. The good news is that you can determine whether the other data member has (or has not) been initialized using some straightforward language rules that are independent of the particular compiler you're using. The bad news is that you have to know those language rules (e.g., base class sub-objects are initialized first (look up the order if you have multiple and/or virtual inheritance!), then data members defined in the class are initialized in the order in which they appear in the class declaration). If you don't know these rules, then don't pass any data member from the this object (regardless of whether or not you explicitly use the thiskeyword) to any other data member's initializer! And if you do know the rules, please be careful.

查看更多
我只想做你的唯一
6楼-- · 2019-01-24 00:12

It's fine, as long as you can start using that pointer right away. If you require the rest of the constructor to complete initialization before the new thread can use the pointer, then you need to do some synchronization.

查看更多
Lonely孤独者°
7楼-- · 2019-01-24 00:14

Depends on what you do after starting the thread. If you perform initialization work after the thread has started, then it could use data that is not properly initialized.

You can reduce the risks by using a factory method that first creates an object, then starts the thread.

But I think the greatest flaw in the design is that, for me at least, a constructor that does more than "construction" seems quite confusing.

查看更多
登录 后发表回答