简体   繁体   中英

passing this from constructor initializer list

Are there any issues when passing 'this' to another object in the initializer list in the following code?

class Callback { public: virtual void DoCallback() = 0; }; 

class B
{
    Callback& cb;
public:
    B(Callback& callback) : cb(callback) {}
    void StartThread();

    static void Thread() 
    {
        while (!Shutdown())
        {
            WaitForSomething();
            cb.DoCallback();
        }
     }
};

class A : public Callback
{
    B b;
public:
    A() : b(*this) {b.StartThread();}
    void DoCallback() {} 
};

If it is unsafe to do that, what's the best alternative?

If you are extremely careful this will work fine. You will get into a lot of trouble if you start calling virtual methods or using methods which depend on other objects in the type. But if you're just setting a reference this should work fine.

A safer (but not completely safe) alternative is to set b later on once the constructor is completed. This won't eliminate vtable issues but will remove problems like accessing other member variables before they are constructed.

class A : public Callback {
  std::auto_ptr<B> spB;
public:
  A() {
    spB.reset(new B(this));
    spB->StartThread();
  }
};

If the other class just stores the pointer/reference, like in your case, it is safe. But you have to be sure that the constructors/functions you pass this to are not trying to access the referenced object before A 's constructor finishes. The A object is not fully constructed yet and calling methods of A and accessing properties might lead to undefined results.

Its generally safe if you are just storing away the pointer for later use. I've done this. I would not do any of the following:

  • Use it to access base or derived class data (they may not have had their constructors run)
  • Do anything polymorphic, the vtable may not be initialized.

Here is a great article from the C++ FAQ which details the problems with "this" in constructors.

Starting threads in constructors that run code in the still-under-construction object is dangerous. The code you presented will work properly as-is, but the solution is fragile.

If DoCallback calls a virtual method in A then you might end up with unexpected results depending on how fast the thread runs. If the called method is pure virtual then the application will die, if it is not pure, the A version of the method will be called instead of the derived version. That is exactly the same reason you should never call a virtual method from a constructor.

The safer approach is having the user call the thread. That is also the approach in boost::thread library and the upcoming standard. Create and initialize the object that is going to be executed and then pass it to the running thread:

class Worker
{
public:
   void DoWork();
};
void startWorkerThread()
{
   Worker w; // fully create the object that is going to be run before you...
   boost::thread thr( boost::bind( &Worker::DoWork, &w ) ); // ...create thread and run
}

It is safe enough, provided you keep in mind that the this object has not yet been fully constructed. If your B class just stores the pointer, without calling any functions on it in the constructor, you're safe. If you try to access the pointer from B's constructor, you have to be extremely careful and pay attention to the order in which A's members are initialized.

As long as you make sure the reference is still valid, this is very similar to an idiom called double dispatch. It might be overkill in this case, but there's nothing inherently wrong with it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM