C++ using this pointer in constructors

后端 未结 8 994
时光取名叫无心
时光取名叫无心 2020-12-15 16:45

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, call

相关标签:
8条回答
  • 2020-12-15 17:18

    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.

    0 讨论(0)
  • 2020-12-15 17:24

    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.

    0 讨论(0)
  • 2020-12-15 17:25

    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();
      // ...
    }
    
    0 讨论(0)
  • 2020-12-15 17:29

    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.

    0 讨论(0)
  • 2020-12-15 17:34

    It can be potentially dangerous.

    During construction of a base class any calls to virtual functions will not despatch to overrides in more derived classes that haven't yet been completely constructed; once the construction of the more derived classes change this changes.

    If the thread that you kick-off calls a virtual function and it is indeterminate where this happens in relation to the completion of the construction of the class then you are likely to get unpredictable behaviour; perhaps a crash.

    Without virtual functions, if the thread only uses methods and data of the parts of the class that have been constructed completely the behaviour is likely to be predictable.

    0 讨论(0)
  • 2020-12-15 17:35

    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.

    0 讨论(0)
提交回复
热议问题