Why use an initialization method instead of a constructor?
Asked Answered
L

12

65

I just got into a new company and much of the code base uses initialization methods instead of constructors.

struct MyFancyClass : theUberClass
{
    MyFancyClass();
    ~MyFancyClass();
    resultType initMyFancyClass(fancyArgument arg1, classyArgument arg2, 
                                redundantArgument arg3=TODO);
    // several fancy methods...
};

They told me that this had something to do with timing. That some things have to be done after construction that would fail in the constructor. But most constructors are empty and I don't really see any reason for not using constructors.

So I turn to you, oh wizards of the C++: why would you use an init-method instead of a constructor?

Lathrop answered 24/9, 2010 at 12:4 Comment(7)
If your own company can't even explain it, I smell bad code.Denver
Looks like a void is missing.Lepsy
I agree with Mike. Start looking for a job.Kurman
I am an engineer. The company is an engineering company. Code can be smelly as long as the algorithms are awesome. And they are! (If I had asked further, they would have explained it further. Its just that you guys can explain it better and faster, so I asked you instead ;-))Lathrop
See this link at Wiki on the latest C++ standard as it resolves the one valid reason for init functions : en.wikipedia.org/wiki/C%2B%2B11#Object_construction_improvementHapten
Related C++ Idiom: Calling virtuals during init / two phase initialization.Ella
For a database connection wrapper class, you define constructor and then init method that connects to databaseFearsome
F
76

Since they say "timing", I guess it's because they want their init functions to be able to call virtual functions on the object. This doesn't always work in a constructor, because in the constructor of the base class, the derived class part of the object "doesn't exist yet", and in particular you can't access virtual functions defined in the derived class. Instead, the base class version of the function is called, if defined. If it's not defined, (implying that the function is pure virtual), you get undefined behavior.

The other common reason for init functions is a desire to avoid exceptions, but that's a pretty old-school programming style (and whether it's a good idea is a whole argument of its own). It has nothing to do with things that can't work in a constructor, rather to do with the fact that constructors can't return an error value if something fails. So to the extent that your colleagues have given you the real reasons, I suspect this isn't it.

Frasco answered 24/9, 2010 at 12:10 Comment(4)
Just an Msdn reference for your post : msdn.microsoft.com/en-us/library/ms182331%28VS.80%29.aspxSubcontinent
The first one is indeed a valid reason. It's also known a two-phase-construction. If I need something like this, I hide it in the innards of an object. I would never expose this to the users of my class.Kurman
As for the 2nd reason, the constructor can still be used with helper functions to manage the resources where there can be an exception (if exceptions are enabled). If the constructor fails, then the destructor will not be called on the partially constructed object. This is why sometimes you see manual init/cleanup code.Gomes
@brita_: yes, although arguably that's a problem with their code that they should fix by throwing more RAII at it. If the constructor fails then the destructors of the class's non-static data members are all called (just those which have already been initialized, in the case the failure occurs during initialization of one of them). Therefore, if you use instance data and/or variables within the constructor to capture anything that needs cleaning, then you wouldn't need any manual cleanup for an exception. Sometimes easier said than done, though.Frasco
T
36

Yes I can think of several, but generally it's not a good idea.

Most of the times the reason invoked is that you only report errors through exceptions in a constructor (which is true) whereas with a classic method you can return an error code.

However in properly designed OO-code the constructor is responsible for establishing the class invariants. By allowing a default constructor, you allow an empty class, thus you have to modify the invariants so that is accepted both the "null" class and the "meaningful" class... and each use of the class must first ensure that the object has been properly built... it's crass.

So now, let's debunk the "reasons":

  • I need to use a virtual method: use the Virtual Constructor idiom.
  • There is a lot of work to be done: so what, the work will be done anyway, just do it in the constructor
  • The setup may fail: throw an exception
  • I want to keep the partially initialized object: use a try/catch within the constructor and set the error cause in an object field, don't forget to assert at the beginning of each public method to make sure the object is usable before trying to use it.
  • I want to reinitialize my object: invoke the initialization method from the constructor, you'll avoid duplicate code while still having a fully initialized object
  • I want to reinitialize my object (2): use operator= (and implement it using the copy and swap idiom if the compiler generated version does not suit your need).

As said, in general, bad idea. If you really want to have "void" constructor, make them private and use Builder methods. It's as efficient with NRVO... and you can return boost::optional<FancyObject> in case the construction failed.

Tepid answered 24/9, 2010 at 12:18 Comment(5)
I'd go for the alternative debunkment, "I want the possibility to reinitialize my object: implement a copy-and-swap operator="Frasco
I thought about it, then realized that there may be optimization opportunities. For example, think of the assign method on a vector object: by using assign you re-use the already allocated storage and thus do not require a spurious memory allocation. It's tricky to do it correctly though, so I agree that copy and swap should be used in the general case.Tepid
agreed, it's not wrong to have a re-initialize function, it's just not what I'd start with. If nothing else, for "optimization opportunity" read "basic exception guarantee".Frasco
@Steve: yes we agree, as said "It's tricky to do it correctly" :-) I guess it's one of those cases where using the STL as an example might not have been the wisest choice since it's written by expert programmers, not average ones.Tepid
vector::assign is a re-initialization method, and follows your advice above.Mineralogy
W
17

Others have listed lots of possible reasons (and proper explanations of why most of these are generally not a good idea). Let me post one example of a (more or less) valid use of init methods, which actually has to do with timing.

In a previous project, we had lots of Service classes and objects, each of which were part of a hierarchy, and cross referencing each other in various ways. So typically, for creating a ServiceA, you needed a parent service object, which in turn needed a service container, which already depended on the presence of some specific services (possibly including ServiceA itself) at initialization time. The reason was that during initialization, most of the services registered itself with other services as listeners to specific events, and/or notified other services about the event of successful initialization. If the other service did not exist at the time of notification, the registration did not happen, thus this service would not receive important messages later, during the usage of the application. In order to break the chain of circular dependencies, we had to use explicit initialization methods separate from constructors, thus effectively making global service initialization a two-phase process.

So, although this idiom should not be followed in general, IMHO it has some valid uses. However, it is best to limit its usage to the minimum, using constructors whenever possible. In our case, this was a legacy project, and we didn't yet fully understand its architecture. At least the usage of init methods was limited to the service classes - regular classes were initialized via constructors. I believe there might be a way to refactor that architecture to eliminate the need for service init methods, but at least I did not see how to do it (and to be frank, we had more urgent issues to deal with at the time I was part of the project).

Worthen answered 24/9, 2010 at 12:23 Comment(6)
Sounds like you were using singletons. (Don't do that.)Mineralogy
@Roger, no, these were not Singletons. You could in fact have multiple so called "models" open at the same time, each containing a single dedicated instance of each Service type. I am well aware of the problems with Singletons, but thanks nevertheless :-)Oven
it's only 2 phases initialization if you consider that the registration of listeners is part of the initialization. In a dynamic view of the world, where services pop and go, the registration of listeners is part of the life of the system.Tepid
@Matthieu, this was not so dynamic a system - the set of possible existing services and their relations was strictly hard-coded, and most of them just had to be present in every model. We could've tried to refactor the system to start moving into that direction though, but that would have been a huge endeavour for which we simply had no resources.Oven
Ah resources... we all got there :)Tepid
@Roger Pate Why not using singletons? In fact there may exist many manager objects as singletons in a system, and they may rely on each other similarly to the situation mentioned above.Serrell
N
9

Two reasons I can think of off the top of my head:

  • Say creating an object involves lots and lots of tedious work that can fail in lots and lots of horrible and subtle ways. If you use a short constructor to set up rudamentary things that won't fail, and then have the user call an initialization method to do the big job, you can at least be sure that you have some object created even if the big job fails. Maybe the object contains information about precisely what way the init failed, or maybe it's important to keep unsuccessfully initialized objects around for other reasons.
  • Sometimes you might want to reinitialize an object long after it has been created. In this way, it's just a matter of calling the initialization method again without destroying and recreating the object.
Nelsen answered 24/9, 2010 at 12:11 Comment(3)
+1 I thought about your second argument; indeed I think this is nevertheless considered a bad practice in C++ and it should be made using some helper objects.Drinking
Your second bullet is probably the most common reason why I sometimes use initializer functions. Yes, the constructor could call the init, but that just makes the code less clear.Oho
These are typical reasons to see an init or a cleanup function, but it would then not be a part of the class public interface, being an internal helper function to avoid code deduplication. Other posts cover reasons to do this systemically.Gomes
J
5

One more use of such initialization can be in object pool. Basically you just request the object from the pool. The pool will have already some N objects created which are blank. It's the caller now which can call any method he/she likes to set the members. Once caller has done with the object it will tell the pool to destory it. The advantage is until the object is being used the memory will be saved, and the caller can use it's own suitable member method of initializing the object. An object may be serving a lot of purpose but the caller may not need all, and also may not need to initialize all the member of the objects.

Typically think of database connections. A pool can have bunch of connection object, and the caller can fill the username, password etc.

Jannajannel answered 24/9, 2010 at 19:34 Comment(0)
A
5

init() function are good when your compiler doesn't support exceptions, or your target application cannot use a heap (exception are usually implemented using a heap to create and destroy them).

init() routines are also useful when the order of construction needs to be defined. That is to say, if you globally allocate objects, the order in which the constructor is invoked is not defined. For instance:

[file1.cpp]
some_class instance1; //global instance

[file2.cpp]
other_class must_construct_before_instance1; //global instance

The standard provides no guarantee that must_construct_before_instance1's constructor will be invoked before instance1's constructor. When it's tied to hardware, order in which things initialize can be crucial.

Adala answered 9/7, 2014 at 19:19 Comment(0)
S
1

And also I like to attach a code sample to answer #1 --

Since also msdn says :

When a virtual method is called, the actual type that executes the method is not selected until run time. When a constructor calls a virtual method, it is possible that the constructor for the instance that invokes the method has not executed.

Example : The following example demonstrates the effect of violating this rule. The test application creates an instance of DerivedType, which causes its base class (BadlyConstructedType) constructor to execute. BadlyConstructedType's constructor incorrectly calls the virtual method DoSomething. As the output shows, DerivedType.DoSomething() executes, and does so before DerivedType's constructor executes.

using System;

namespace UsageLibrary
{
    public class BadlyConstructedType
    {
        protected  string initialized = "No";

        public BadlyConstructedType()
        {
            Console.WriteLine("Calling base ctor.");
            // Violates rule: DoNotCallOverridableMethodsInConstructors.
            DoSomething();
        }
        // This will be overridden in the derived type.
        public virtual void DoSomething()
        {
            Console.WriteLine ("Base DoSomething");
        }
    }

    public class DerivedType : BadlyConstructedType
    {
        public DerivedType ()
        {
            Console.WriteLine("Calling derived ctor.");
            initialized = "Yes";
        }
        public override void DoSomething()
        {
            Console.WriteLine("Derived DoSomething is called - initialized ? {0}", initialized);
        }
    }

    public class TestBadlyConstructedType
    {
        public static void Main()
        {
            DerivedType derivedInstance = new DerivedType();
        }
    }
}

Output :

Calling base ctor.

Derived DoSomething is called - initialized ? No

Calling derived ctor.

Subcontinent answered 24/9, 2010 at 12:57 Comment(0)
A
1

More of a special case: If you create a listener, you might want to make it register itself somewhere (such as with a singleton or GUI). If you do that during its constructor, it leaks a pointer/reference to itself which is not yet safe, since the constructor has not completed (and might even fail completely). Assume the singleton that collects all listeners and sends them events when things happen receives and event, and then loops through its list of listeners (one of them is the instance we are talking about), to send them each a message. But this instance is still mid-way in its constructor, so the call can fail in all kinds of bad ways. In this case, it makes sense to have registration in a separate function, which you obviously do not call from the constructor itself (that would defeat the purpose entirely), but from the parent object, after construction has completed.

But that is a specific case, not the general one.

Atalya answered 24/9, 2010 at 15:37 Comment(0)
A
1

It's useful for doing resource management. Say you have classes with destructors to automatically deallocate resources when the object's lifetime is over. Say you also have a class that holds these resource classes, and you initiate them in the constructor of this upper class. What happens when you use the assignment operator to initiate this higher class? Once the contents are copied, the old higher class becomes out of context, and the destructors are called for all the resource classes. If these resource classes have pointers that were copied during assignment, then all these pointers are now bad pointers. If you instead initiate the resource classes in a seperate init function in the higher class, you completely bypass the resource class' destructor from ever being called, because the assignment operator never has to create and delete these classes. I believe this is what was meant by the "timing" requirement.

Amberjack answered 24/7, 2012 at 2:28 Comment(0)
N
0

A few more cases:

COOKING ARGS

A constructor can't call another constructor, but an init method can call another init.

For instance say we have a initializer that takes a list of conventional args. We have another initializer that takes a dictionary of name=value pairs. The second can query the dictionary for the arguments the first initializer accepts and call the first with them.

This is fine when the initializers are an init methods, but not when the initializers are constructors.

CHICKEN OR EGG

We might have a car class whose initializer must have a pointer to a motor object, and the motor class initializer must have a pointer to its car object. This is simply impossible with constructors, but trivial with init methods.

BREAKING UP ARG LIST

There may be a huge number of args that could be specified but don't need to be (perhaps the default values suffice, or perhaps some parameters are only needed depending on values for other parameters). We may wish to have several initializers instead of a single one.

Again, it's simply impossible to break up a constructor, but trivial to break up an initializer.

Nonsuit answered 18/10, 2019 at 1:1 Comment(0)
A
0

Another use case:

You have an object pool, like a simple vector of objects (not a vector of pointers, but actual stack allocated objects tightly packed).

The object class is complex, with many private member variables, which all needs to be set properly.

As such, each object is properly constructed upon insertion (using constructor, with many parameters).

So far, the init function is not in the picture, a constructor can do this so far.

Due to an external event, one of the object in the middle has to be:

  • removed from the pool, basically destroyed
  • and a new element has to be created with different parameters.

You don't want to do memory operation on that single element, like delete/new to invoke the constructor/destructor, that's expensive. What you want is dispose and reinitialize the existing objects fully, with the new parameters, so no reallocation occurs.

This can be done with getter setter functions for each variable, but that doesn't take care of the dispose part, also its very easy to leave out member variables from the list, and you end up partially setup object. Better to put together an init function which expects all the necessary parameters for its member variables, and reinitialize the whole object simply, over and over again, if needed.

Adama answered 10/9, 2022 at 22:47 Comment(0)
H
-2

You use an initialisation method instead of the constructor if the initialiser needs to be called AFTER the class has been created. So if class A was created as:

A *a = new A;

and the initisalizer of class A required that a be set, then obviously you need something like:

A *a = new A;
a->init();
Heuer answered 24/9, 2010 at 12:10 Comment(3)
I don't buy this. You can just call init privately within the last line of the constructor, then. (I didn't down-vote though)Denver
That's a restatement of the question, not an answer. Why would you need to initialise the object after construction?Dace
If the constructor relies on the variable a being set, it's happened to me.Heuer

© 2022 - 2024 — McMap. All rights reserved.