What (not) to do in a constructor

后端 未结 13 1658
悲&欢浪女
悲&欢浪女 2020-12-12 17:33

I want to ask you for your best practices regarding constructors in C++. I am not quite sure what I should do in a constructor and what not.

Should I only use it for

相关标签:
13条回答
  • 2020-12-12 17:40
    • Don't call delete this or the destructor in the constructor.
    • Don't use init()/cleanup() members. If you have to call init() every time you create an instance, everything in init() should be in the constructor. The constructor is meant to put the instance into a consistent state which allows any public member to be called with a well-defined behavior. Similarly for cleanup(), plus cleanup() kills RAII. (However, when you have multiple constructors, it's often useful to have a private init() function that's called by the them.)
    • Doing more complex things in constructors is okay, depending on the classes' intended use and your overall design. For example, it wouldn't be a good idea to read a file in the constructor of some kind of Integer or Point class; users expect those to be cheap to create. It's also important to consider how file-accessing constructors will impact your ability to write unit tests. The best solution is usually to have a constructor that just takes the data that it needs to construct the members and write a non-member function that does the file parsing and returns an instance.
    0 讨论(0)
  • 2020-12-12 17:40

    From The C++ Programming Language:

    The use of functions such as init() to provide initialization for class objects is inelegant and errorprone. Because it is nowhere stated that an object must be initialized, a programmer can forget to do so – or do so twice (often with equally disastrous results). A better approach is to allow the programmer to declare a function with the explicit purpose of initializing objects. Because such a function constructs values of a given type, it is called a constructor.

    I usually consider the following rule when designing a class: I must be able to use any method of the class safely after the constructor has executed. Safe here means you could always throw exceptions if the object's init() method has not been called, but I prefer to have something that is actually usable.

    For instance, class std::string might not allocate any memory when you use the default constructor because most methods (i.e. begin() and end()) would work correctly if both return null pointers, and c_str() does not necessarily return the current buffer for other design reasons, therefore it has to be prepared to allocate memory at any time. Not allocating memory in this case still leads to a perfectly usable string instance.

    Conversely, use of RAII in scoped guards for mutex locks is an example of a constructor that may execute for an arbitrarily long time (until the lock's owner releases it) yet is still commonly accepted as good practice.

    In any case, lazy initialization may be done in safer ways than using an init() method. One way is to use some intermediate class that captures all parameters to the constructor. Another is to use the builder pattern.

    0 讨论(0)
  • 2020-12-12 17:44

    You MAY throw from a constructor, and it is often the better option than creating a zombie object, i.e. an object that has a "failed" state.

    You should, however, never throw from a destructor.

    The compiler WILL know what order the member objects are constructed - the order they appear in the header. The destructor will however not be called as you said, which means if you are calling new multiple times within a constructor you cannot rely on your destructor calling the deletes for you. If you put them into smart pointer objects that is not a problem as these objects will be deleted. If you want them as raw pointers then put them temporarily into auto_ptr objects until you know your constructor will no longer throw, then call release() on all your auto_ptrs.

    0 讨论(0)
  • 2020-12-12 17:47

    Complex logic and constructor do not always mix well, and there are strong proponents against doing heavy work in a constructor (with reasons).

    The cardinal rule is that the constructor should yield a fully usable object.

    class Vector
    {
    public:
      Vector(): mSize(10), mData(new int[mSize]) {}
    private:
      size_t mSize;
      int mData[];
    };
    

    It does not mean a fully initialized object, you may defer some initialization (think lazy) as long as the user does not have to think about it.

    class Vector
    {
    public:
      Vector(): mSize(0), mData(0) {}
    
      // first call to access element should grab memory
    
    private:
      size_t mSize;
      int mData[];
    };
    

    If there is heavy work to be done, you might choose to proceed with a builder method, that will do the heavy work prior to calling the constructor. For example, imagine retrieving settings from a database and building a setting object.

    // in the constructor
    Setting::Setting()
    {
      // connect
      // retrieve settings
      // close connection (wait, you used RAII right ?)
      // initialize object
    }
    
    // Builder method
    Setting Setting::Build()
    {
      // connect
      // retrieve settings
    
      Setting setting;
      // initialize object
      return setting;
    }
    

    This builder method is useful if postponing the construction of the object yields a significant benefit. From example if the objects grab a lot of memory, postponing the memory acquisition after tasks that are likely to fail may not be a bad idea.

    This builder method implies Private constructor and Public (or friend) Builder. Note that having a Private constructor imposes a number of restrictions on the usages that can be done of a class (cannot be stored in STL containers, for example) so you might need to merge in other patterns. Which is why this method should only be used in exceptional circumstances.

    You might wish to consider how to test such entities too, if you depend on an external thing (file / DB), think about Dependency Injection, it really helps with Unit Testing.

    0 讨论(0)
  • 2020-12-12 17:48

    I would rather ask:

    What all to do in the constructor?
    

    and anything not covered above is answer to the OP's question.

    I think the one and only purpose of the constructor is to

    1. initialize all the member variables to a known state, and

    2. allocate resources (if applicable).

    The item #1 sounds so simple, yet I see that to be forgotten/ignored on regular basis and only to be reminded by a static analysis tool. Never underestimate this (pun intended).

    0 讨论(0)
  • 2020-12-12 17:50

    Ideally, you should have no code in your constuctors, ever (aside from attribute assignment). There is one important reason: It prevents the composition of objects and makes them un-extensible.

    Here is my blog post about this: Constructors Must Be Code-Free

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