I\'m currently having a discussion with my teacher about class design and we came to the point of Initialize()
functions, which he heavily promotes. Example:
Some members simply must have values at construction (e.g. references, const
values, objects designed for RAII without default constructors)... they can't be constructed in the initialise()
function, and some can't be reassigned then.
So, in general it's not a choice of constructor vs. initialise()
, it's a question of whether you'll end up having code split between the two.
Of bases and members that could be initialised later, for the derived class to do it implies they're not private
; if you go so far as to make bases/members non-private
for the sake of delaying initialisaton you break encapsulation - one of the core principles of OOP. Breaking encapsulation prevents base class developer(s) from reasoning about the invariants the class should protect; they can't develop their code without risking breaking derived classes - which they might not have visibility into.
Other times it's possible but sometimes inefficient if you must default construct a base or member with a value you'll never use, then assign it a different value soon after. The optimiser may help - particularly if both functions are inlined and called in quick succession - but may not.
- [constructors] can't be overridden by derived classes
...so you can actually rely on them doing what the base class needs...
- [constructors] can't call virtual functions
The CRTP allows derived classes to inject functionality - that's typically a better option than a separate initialise()
routine, being faster.
Arguments for Initialize() functions:
- derived class can completely replace initialization code
I'd say that's an argument against, as above.
- derived class can do the base class initialization at any time during its own initialization
That's flexible but risky - if the base class isn't initialised the derived class could easily end up (due to oversight during the evolution of the code) calling something that relies on that base being initialised and consequently fails at run time.
More generally, there's the question of reliable invocation, usage and error handling. With initialise
, client code has to remember to call it with failures evident at runtime not compile time. Issues may be reported using return types instead of exceptions or state, which can sometimes be better.
If initialise()
needs to be called to set say a pointer to nullptr
or a value safe for the destructor to delete
, but some other data member or code throws first, all hell breaks loose.
initialise()
also forces the entire class to be non-const
in the client code, even if the client just wants to create an initial state and ensure it won't be further modified - basically you've thrown const
-correctness out the window.
Code doing things like p_x = new X(values, for, initialisation);
, f(X(values, for initialisation)
, v.push_back(X(values, for initialisation))
won't be possible - forcing verbose and clumsy alternatives.
If a destroy()
function is also used, many of the above problems are exacerbated.
Not calling Initialize
may be easy to do accidentally and won't give you a properly constructed object. It also doesn't follow the RAII principle since there are separate steps in constructing/destructing the object: What happens if Initialize
fails (how do you deal with the invalid object)?
By forcing default initialization you may end up doing more work than doing initialization in the constructor proper.
While I agree with the downsides of doing initialization exclusively in the constructor, I do think that those are actually signs of bad design.
A deriving class should not need to override base class initialization behaviour entirely. This is a design flaw which should be cured, rather than introducing Initialize()
-functions as a workaround.
If you use it, then you should make the constructor private and use factory methods instead that call the initialize()
method for you. For example:
class MyClass
{
public:
static std::unique_ptr<MyClass> Create()
{
std::unique_ptr<MyClass> result(new MyClass);
result->initialize();
return result;
}
private:
MyClass();
void initialize();
};
That said, initializer methods are not very elegant, but they can be useful for the exact reasons your teacher said. I would not consider them 'wrong' per se. If your design is good then you probably will never need them. However, real-life code sometimes forces you to make compromises.
Others have argued at length against the use of Initialize
, I myself see one use: laziness.
For example:
File file("/tmp/xxx");
foo(file);
Now, if foo
never uses file
(after all), then it's completely unnecessary to try and read it (and would indeed be a waste of resources).
In this situation, I support Lazy Initialization, however it should not rely on the client calling the function, but rather each member function should check if it is necessary to initialize or not. In this example name()
does not require it, but encoding()
does.
For Initialize
: exactly what your teacher says, but in well-designed code you'll probably never need it.
Against: non-standard, may defeat the purpose of a constructor if used spuriously. More importantly: client needs to remember to call Initialize
. So, either instances will be in an inconsistent state upon construction, or they need lots of extra bookkeeping to prevent client code from calling anything else:
void Foo::im_a_method()
{
if (!fully_initialized)
throw Unitialized("Foo::im_a_method called before Initialize");
// do actual work
}
The only way to prevent this kind of code is to start using factory functions. So, if you use Initialize
in every class, you'll need a factory for every hierarchy.
In other words: don't do this if it's not necessary; always check if the code can be redesigned in terms of standard constructs. And certainly don't add a public Destroy
member, that's the destructor's task. Destructors can (and in inheritance situations, must) be virtual
anyway.