What (not) to do in a constructor
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 attribute initializations, calling parent constructors etc.? Or might I even put more complex functions into them like reading and parsing configuration data, setting up external libraries a.s.o.
Or should I write special functions for this? Resp. init()
/ c开发者_Python百科leanup()
?
What are the PRO's and CON's here?
I figured out yet that for example I can get rid of shared pointers when using init()
and cleanup()
. I can create the objects on the stack as class attributes and initialize it later while it is already constructed.
If I handle it in the constructor I need to instantiate it during runtime. Then I need a pointer.
I really don't know how to decide.
Maybe you can help me out?
The most common mistake to do in a constructor as well as in a destructor, is to use polymorphism. Polymorphism often does not work in constructors !
e.g.:
class A
{
public:
A(){ doA();}
virtual void doA(){};
}
class B : public A
{
public:
virtual void doA(){ doB();};
void doB(){};
}
void testB()
{
B b; // this WON'T call doB();
}
this is because the object B is not yet constructed while performing the constructor of the mother class A... thus impossible for it to call the overriden version of void doA();
An example where polymorphism will work in constructor:
class A
{
public:
void callAPolymorphicBehaviour()
{
doOverridenBehaviour();
}
virtual void doOverridenBehaviour()
{
doA();
}
void doA(){}
};
class B : public A
{
public:
B()
{
callAPolymorphicBehaviour();
}
virtual void doOverridenBehaviour()
{
doB()
}
void doB(){}
};
void testB()
{
B b; // this WILL call doB();
}
This time, the reason behind is: at the time the virtual
function doOverridenBehaviour()
is invoked, the object b is already initialized (but not yet constructed), this means that its virtual table is initialized, and thus can perform polymorphism.
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.
- 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.
Simple answer: it depends.
When designing your software you should might want to program via the RAII principle ("Resource Acquisition is initialization"). This means (among other things) that the object itself is responsible for its resources, and not the caller. Also, you might want to familiarize yourself with exception safety (in their different degrees).
For example, consider:
void func() { MyFile f("myfile.dat"); doSomething(f); }
If you design the class MyFile
in the way, that you can be sure before doSomething(f)
that f
is initialized, you save a lot of trouble checking for that. Also, if you release the ressources held by f
in the destructor, i.e. close the file handle, you are on the safe side and it is easy to use.
In this specific case you can use the special properties of constructors:
- If you throw an exception from the constructor to its outside world, the object will not be created. This means, the destructor will not be called and the memory will be freed immediately.
- A constructor must be called. You can not force the user to use any other function (except the destructor), only by convention. So, if you want to force the user to initialize your object, why not via the constructor?
- If you have any
virtual
methods, you should not call those from inside the constructor, unless you know what you are doing -- you (or later users) might get surprised why the virtual overriding method is not called. Best not to confuse anyone.
A constructor must leave your object in a usable state. And because it is aways wise to make it difficult to use your API wrong, the best thing to do is make it easy to be used correct (sic to Scott Meyers). Doing initialization inside the constructor should be your default strategy -- but there are always exceptions, of course.
So: It is a good way to use constructors for initialization. It is not always possible, for example GUI frameworks often need to be constructed, then initialized. But if you design your software completely in this direction, you can save a lot of trouble.
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.
A constructor is expected to create an object that can be used from the word go. If for some reason it is not able to create a usable object, it should throw an exception and be done with it. So, all supplementary methods/functions that are necessary for an object to work properly should be called from the constructor (unless you want to have lazy-loading like features)
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
initialize all the member variables to a known state, and
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).
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.
I think the most important thing is a bit of common sense! There's lots of talk about do's and dont's - all well and good, but the key point to consider is how your object will be used. For example,
- how many instances of this object will be created? (are they held in containers for example?)
- how often are they created and destroyed? (loops?)
- how big is it?
If this object is a single instance, that is constructed at the start and the construction is not in the critical path - why not do the heavy work in the constructor (as long as you use exceptions appropriately, it may even make more sense)? If on the other hand, it's a very light weight object that is created and destroyed in a loop for a short period of time - try do as little as possible (aside from initializing the members, for example) (functors are a very good example of this...)
There are advantages to having a two phase load (or whatever), but main disadvantage is forgetting to call it - how many of us have done that?? :)
So, my tuppence is, don't stick to a hard and fast rule, look carefully at how your object is to be used, and then design it to suit!
A constructor is used to construct an object - nothing more and nothing less. You need to do whatever it takes to establish the class invariants within a constructor and how complex that is it really depends on the type of the object being initialized.
Separate init() functions are a good idea only if for some reason you can't use exceptions.
Well, «constructor» comes from construction, building, setting up. So there's where all initialization happens. Whenever you instance a class, use the constructor to ensure that everything is done in order to make the new object workable with.
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
You can do what you want to do, but use constructor for that purpose for what it's call - create object. and if for that need to call others methods, that's Ok. just follow one rule - don't make it complex more than it's need. Good practice is to do constructor as simple as possible, but that not means that you need just initialize members.
精彩评论