Skip to main content
GameDev.net gamedev.net
🔒 Locked

Post-constructor call to an initialization method from base class?

Started by lordikon Dec 29, 2011 at 6:14 PM 11 replies 2.8k views
Original Post
lordikon
lordikon
What I'm trying to achieve is that after my object is finished constructing, the base class makes a call to a virtual function named Initialize(). However the only time I can see to call that method is during the base class' constructor, and if I call it then it will occur before the derived classes constructor has had time to finish, not to mention calling a virtual function during a constructor just seems dangerous, even if it worked the derived class(es) aren't finished initializing. What I'm trying to avoid is having to have all programmers writing derived classes have to remember to call Initialize(), I would prefer that it "just happen" as far as they're concerned.

Here's an example of the flow I would like to achieve.

Class B derives from A

// Programmer only does this
MyObj B = new B();

1.) A's constructor occurs
2.) B's constructor occurs
3.) A calls virtual Initialize() method

// ===============================
// This part occurs if B handles Initialize()
3a.) B receives call to Initialize()
3b.) B calls base class Initialize()
// ===============================

4.) A receives call to Initialize()
SiCrane
SiCrane
This is usually done by using a factory function. The factory creates the object and after it's created the factory calls the virtual function. Generally the constructors are all made protected and the factory is friended.
MikeNicolella
MikeNicolella
Is there a good reason the constructor itself isn't initializing the object?
lordikon
lordikon

This is usually done by using a factory function. The factory creates the object and after it's created the factory calls the virtual function. Generally the constructors are all made protected and the factory is friended.


I've done it this way in the past, I was hoping there was some other way. I might be able to go with a factory this time around as well I guess. I'll have to consider that.



Is there a good reason the constructor itself isn't initializing the object?


I've made it a habit to leave certain initializations out of the constructor for some of my classes. I often have cases where I want to reset an object, or re-initialize it. If I have an initialize method I can call it to reset the object, as well as setup the object when it is first created. I'll usually have constructors call the initialize method.
SiCrane
SiCrane
There is another way, though I don't particularly recommend it. Make all your constructors protected as with the factory method and instead friend a template class:

template <typename T>
struct Initializer : T {
Initializer() { initialize(); }
};

Then instead of going Foo * foo = new Foo(); use Foo * foo = new Initializer(); Unless you're doing something like comparing typeids directly (which you shouldn't) or similar polymorphism no-nos, it should work pretty much the same as the factory.
the_edd
the_edd
You could also consider extracting the virtual parts in to a "strategy object". What was the base object would now get given the strategy object at construction time and can call any virtual functions on it, as needed, in order to perform initialization.
jwezorek
jwezorek
The other thing you can do use a prototype-style factory object i.e. one that creates an object of a particular type by cloning a 'prototype' object of that type. With this kind of factory, you can use 'the curiously recurring template pattern' to implement the clone() and/or copy() member functions in the base class of your object hierarchy and then get these functions for free without boilerplate in derived classes. Obviously, call the polymorphic initialize() member function in the clone() or copy().
Antheus
Antheus
Protected keyword has mostly fallen out of favor. Experience has shown it breaks encapsulation and has negative impact on design.

The whole concept of "make it protected but make sure it's only called from factory etc..." shows the problem. It also imposes strict order in that specific method must be called in specific order at specific point, or class invariants aren't upheld.

After call to construction, class must be valid and fully initialized. If some inherited concept exists, it must be public and it may be invoked at any time. Having a reset() function doesn't break this.

Strategy pattern was mentioned. Constructor is one concept, post-initialize is another. By having two independent concepts such design breaks single responsibility rule.

Assuming something like this:class Socket {
Socket();
virtual initialize();
};
class UDPSocket() : Socket {};
class TCPSocket() : socket {};
Construction takes two phases. First one, say, creates socket resources, second one perhaps binds the socket.

Refactoring is somewhat mechanical. We have two individual operations called in sequence. Thereby, the compile-time version:
class SocketResources {};
class SocketBinder {};

template <class Resource, class Binder >
class Socket {
private:
Resource r;
Binder b;
};
typedef Socket<UDPResource, UDPBinder> UDPSocket;
typedef Socket<TCPResource, TCPBinder> TCPSocket;
Alternatively, run-time polymorphism or IoC/DI:class Socket {
Socket(Resource * r, Binder * b) { }
private:
Resource * r;
Binder * b;
}
class UDPSocket : Socket {
UDPSocket() : Socket(new UDPResource(), new UDPBinder()) {};
}
...
Template based version doesn't have a common base class, so different instances cannot be immediately mixed, but at same time doesn't require base classes for composite types.

While it may seem like more code, there is no way getting around the orthogonal inheritance "grid" or "tree". For each additional parameter, number of dimensions increases. By trying to implement them via workarounds in restrictions of C++ type system, they still end up somewhere, most likely in factory method. For two types it's managable - but two types don't really need much of a framework. For anything more, it becomes n^m problem and a design disaster.

Design is linearly extensible (just add more parameters) without increasing complexity. It's also composable - classes with such design can become part of same type of composition without side-effects, due to fully respecting SOLID principles.


Using a factory for such task is inferior option, since instead of using built-in type hierarchy and composition, it requires user to reimplement them.
SiCrane
SiCrane

Protected keyword has mostly fallen out of favor. Experience has shown it breaks encapsulation and has negative impact on design.

I've seen that said for protected data, but not the keyword as a whole. Do you have any sources for that being said for protected functions?
Antheus
Antheus

[quote name='Antheus' timestamp='1325193499' post='4897920']
Protected keyword has mostly fallen out of favor. Experience has shown it breaks encapsulation and has negative impact on design.

I've seen that said for protected data, but not the keyword as a whole. Do you have any sources for that being said for protected functions?
[/quote]
I saw a really nice practical example a while back, but I have no clue where.

Basic idea is that each method that can be invoked outside of your control (inherited classes, callers) needs to preserve invariants, thereby excluding "call in this particular order" scenario.

If something preserves invariants, then it can be called at any time by anyone and becomes public API. Having things like initialize() or reset() for some reason limited to protected serves no design purpose and is merely an implementation detail.

Interfaces accomplish the goal of hiding members by defining the intended public interface while implementation uses different "public" interface. A function operating on IFoo or factory providing IFoo hides all the details while still giving full access to implementors if so desired.

And as pointed above, protected introduces a new soft invariant on how certain members should be used but lacks enforcement.

Only tangential benefit might be IDE code completion scoping to reduce clutter (again interfaces). But clutter is a symptom of complexity, so it can either be reduced, or something is inherently complex.
SiCrane
SiCrane
I don't see how that's an argument against use of protected in this case (or in the general case). In this particular case, the intialize/reset function is already public and constructors already have limitations on how and what order they can be invoked and those are enforced by the language itself. Making a constructor protected doesn't change the fundamental nature of being a constructor. You can't call it out of order.
Codarki
Codarki

Here's an example of the flow I would like to achieve.

Class B derives from A

// Programmer only does this
MyObj B = new B();

1.) A's constructor occurs
2.) B's constructor occurs
3.) A calls virtual Initialize() method

// ===============================
// This part occurs if B handles Initialize()
3a.) B receives call to Initialize()
3b.) B calls base class Initialize()
// ===============================

4.) A receives call to Initialize()

Use composition.

Class B owns instance of A

// Programmer only does this
MyObj B = new B();

1.) B's constructor occurs
2.) B creates new instance of A, and A's constructor occurs
3.) B calls A's Initialize() method
lordikon
lordikon
Thanks for all the replies everyone, I definitely have some design to think about.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.