Lets say I have a class encapsulating one (or multiple) member(s) which must in some way be initialized and there is no reasonable way to use the class without it (so I don't want to make it optional). Is it then better to have initialization run in its constructor like so:
class MyClass
{
MyClass()
{
if(!obj.initialize()
throw ...;
}
private:
MyObject obj;
}
or would you suggest the following design:
class MyClass
{
MyClass()
{
}
bool initialize()
{
return obj.initialize();
}
private:
MyObject obj;
}
The 1st one looks appealing because I can guarantee all requirements for using my class have been met after the constructor has run and I can report any errors by throwing an exception.
The 2nd looks good because it doesn't overload constructors with stuff that intuitively doesn't belong there, especially once the initialization routine gets complex and i.e. creates widgets, opens database connections, initializes 3rd party libraries etc. In a lot of legacy code I'm working with, ctors are flooded with parameters and intialization stuff, probably running thousands of code lines before this type of bloated object construction is even done. Trying to refactor these into something cleaner is really hard at some point because there are too many dependencies involved. That's what motivated my question.
The big disadvantage of design #2 I can see is that I need a public initialization routine which the client must remember to call. But since this can and probably will be forgotten, I have to track and check intialization state in all public members and somehow treat errors (probably an assert will do). This will also clutter my class with stuff that's not there if I chose design #1.
So what's my best choice here?
"The 1st one looks appealing because I can guarantee all requirements for using my class have been met after the constructor has run ...."
This must be the case otherwise the design is bad.
When the constructor completes then the object must be usable without any undefined behaviour and according to its interface specification.
BUT that does not mean the object needs to be configured for a given purpose.
I like to separate out initialization from configuration.
For example look at std::fstream
. You can create a completely initialized fstream object without opening any files:
std::fstream fs; // initialized but not configured
It does not exhibit undefined behaviour and will operate according to its interface specification.
So you can use its interface to configure it to a given purpose - for example to read a specific file:
fs.open("myfile.txt", std::ios::in); // configured
The default constructor should do the absolute minimum to put the object into working order without, necessarily configuring it to a given task.
That being said there is no reason not to have other constructors to make creating configured objects easier:
std::fstream fs("myfile.txt", std::ios::in); // initialized & configured
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With