Should I use virtual 'Initialize()' functions to initialize an object of my class?
Asked Answered
S

13

16

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:

class Foo{
public:
  Foo()
  { // acquire light-weight resources only / default initialize
  }

  virtual void Initialize()
  { // do allocation, acquire heavy-weight resources, load data from disk
  }

  // optionally provide a Destroy() function
  // virtual void Destroy(){ /*...*/ }
};

Everything with optional parameters of course.

Now, he also puts emphasis on extendability and usage in class hierarchies (he's a game developer and his company sells a game engine), with the following arguments (taken verbatim, only translated):

Arguments against constructors:

  • can't be overridden by derived classes
  • can't call virtual functions

Arguments for Initialize() functions:

  • derived class can completely replace initialization code
  • derived class can do the base class initialization at any time during its own initialization

I have always been taught to do the real initialization directly in the constructor and to not provide such Initialize() functions. That said, I for sure don't have as much experience as he does when it comes to deploying a library / engine, so I thought I'd ask at good ol' SO.

So, what exactly are the arguments for and against such Initialize() functions? Does it depend on the environment where it should be used? If yes, please provide reasonings for library / engine developers or, if you can, even game developer in general.


Edit: I should have mentioned, that such classes will be used as member variables in other classes only, as anything else wouldn't make sense for them. Sorry.

Sorenson answered 24/6, 2011 at 16:55 Comment(7)
Run away from there, a C++ teacher that is against RAII is scaryEffeminacy
+1 @CharlesB, looking for the +2 button.Laureen
In complete agreement with CharlesB and larsmans: Flee from any C++ teacher that work against RAII.Diuretic
@CharlesB: See my little edit at the end, I never said it was against RAII. :)Sorenson
@Xeo: RAII is not only for public API, but also for internal design, so it doesn't change anything to my comment :)Effeminacy
Two-step initialization leads to the dark side. Just don't do it.Pedropedrotti
I often find myself in situations where I want to call virtual methods in a constructor. Like the base class is a template for the steps to construct something and derived classes implement it. How can you achieve this without an initialize() method? Maybe some examples would help for how to redesign problems to avoid this....Keyway
L
7

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.

Laureen answered 24/6, 2011 at 17:0 Comment(0)
C
5

I"m against 'double initialization' in C++ whatsoever.

Arguments against constructors:

  • can't be overridden by derived classes
  • can't call virtual functions

If you have to write such code, it means your design is wrong (e.g. MFC). Design your base class so all the necessary information that can be overridden is passed through the parameters of its constructor, so the derived class can override it like this:

Derived::Derived() : Base(GetSomeParameter()) 
{
}
Compose answered 24/6, 2011 at 17:0 Comment(0)
A
5

This is a terrible, terrible idea. Ask yourself- what's the point of the constructor if you just have to call Initialize() later? If the derived class wants to override the base class, then don't derive.

When the constructor finishes, it should make sense to use the object. If it doesn't, you've done it wrong.

Anishaaniso answered 24/6, 2011 at 17:28 Comment(0)
I
4

One argument for preferring initialization in the constructor: it makes it easier to ensure that every object has a valid state. Using two-phase initialization, there's a window where the object is ill-formed.

One argument against using the constructor is that the only way of signalling a problem is through throwing an exception; there's no ability to return anything from a constructor.

Another plus for a separate initialization function is that it makes it easier to support multiple constructors with different parameter lists.

As with everything this is really a design decision that should be made with the specific requirements of the problem at hand, rather than making a blanket generalization.

Ioneionesco answered 24/6, 2011 at 16:59 Comment(1)
Another plus for a separate initialization function is that it makes it easier to support multiple constructors with different parameter lists. That can be handled with a private Initializer function. Also, C++11 allows constructors to call each other, making both pointless.Chiachiack
L
3

A voice of dissension is in order here.

  1. You might be working in an environment where you have no choice but to separate construction and initialization. Welcome to my world. Don't tell me to find a different environment; I have no choice. The preferred embodiment of the products I create is not in my hands.

  2. Tell me how to initialize some aspects of object B with respect to object C, other aspects with respect to object A; some aspects of object C with respect to object B, other aspects with respect to object A. The next time around the situation may well be reversed. I won't even get into how to initialize object A. The apparently circular initialization dependencies can be resolved, but not by the constructors.

  3. Similar concerns goes for destruction versus shutdown. The object may need to live past shutdown, it may need to be reused for Monte Carlo purposes, and it might need to be restarted from a checkpoint dumped three months ago. Putting all of the deallocation code directly in the destructor is a very bad idea because it leaks.

Lianne answered 24/6, 2011 at 17:56 Comment(4)
Valid concerns, but still no excuse for a C++ teacher to "heavily promote" (OP's words) this style to students. Even in situation (3) this style seems like an emergency brake rather than a design principle to be pushed.Laureen
This may be pretty late, but reading through all answers again, I got a though on your second point: pointers and new (which can be seen as a kind of Initialize function aswell as Boost.Optional, if you don't want dynamic allocation. The point is, that you let the users choose if they need the "two phase" initialization. On Boost.Optional: The variable may not be optional, but its semantics allow delayed initialization without dynamic allocation. Also, there's no Boost.Delayed class template and the interna would most likely be the same anyways. Thoughts on that appreciated. :)Sorenson
Also, I'll edit my thoughts into the question when I get to access a real PC again...Sorenson
Can you expand on point 3? What's a "Monte Carlo purpose"? I can't imagine where restarting an object would be required. It sounds like what you really want there is placement new. Also, I don't see how deallocation code in a destructor can leak.Chiachiack
M
1

Forget about the Initialize() function - that is the job of the constructor.

When an object is created, if the construction passed successfully (no exception thrown), the object should be fully initialized.

Mena answered 24/6, 2011 at 16:59 Comment(0)
S
1

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.

Soong answered 24/6, 2011 at 17:2 Comment(0)
S
1

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.

Sacramentarian answered 24/6, 2011 at 17:3 Comment(0)
D
1

Ignoring the RAII implications, which others have adequately covered, a virtual initialization method greatly complicates your design. You can't have any private data, because for the ability to override the initialization routine to be at all useful, the derived object needs access to it. So now the class's invariants are required to be maintained not only by the class, but by every class that inherits from it. Avoiding that sort of burden is part of the point behind inheritance in the first place, and the reason constructors work the way they do with regard to subobject creation.

Digged answered 24/6, 2011 at 17:48 Comment(0)
C
1

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.

Cilurzo answered 24/6, 2011 at 18:11 Comment(3)
"/tmp/xxx" - so you read your porn with a C++ program, huh?Sorenson
@Xeo: vim, I am all for Ascii Art.Cilurzo
In this sample I see no lazy initalization, merely lazy loading. The object (a file handle) was fully initialized in the constructor, and all functions are fully defined and ready to roll without having called file.Initialize(). It might be that they throw exceptions due to the file failing to load, but that's all defined and intended behavior. vector is another example of a class that is fully constructed, but may not grab any resources.Chiachiack
R
0

Only use initialize function if you don't have the data available at point of creation.

For example, you're dynamically building a model of data, and the data that determines the object hierarchy must be consumed before the data that describes object parameters.

Replica answered 24/6, 2011 at 16:59 Comment(0)
C
0

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.

Cookshop answered 24/6, 2011 at 17:2 Comment(0)
M
0

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.

Molder answered 4/6, 2014 at 3:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.