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

Constructor or Init() Method

Started by psyc_ Dec 29, 2006 at 6:39 AM 4 replies 1.5k views
Original Post
psyc_
psyc_
So I was wondering when writing a piece of code this morning, how do other people decide what to put in a constructor and what to put in another Initialisation method. For example, if in a class you had the following..


class foo
{
private: 
  int m_MyInt;
  SomeClass* pSomeClass;
public:
  foo()
  {
    // If I wanted m_MyInt to have a certain value when I
    // first use my class should I set it here, or use a
    // method to set it which I call from here.

    // Method 1:
    m_MyInt = 2;

    // Method 2:
    Init();

    // The same can be said for allocating memory for the
    // pointer to SomeClass.. should it be done here or 
    // in the Init() method...
  }

  void Init()
  {
    m_MyInt = 2;
  }

}



There are loads of different ways of doing these things, for instance passing an int when creating the class and setting it from that

foo(int inInt)
{
  m_MyInt = inInt;
}


but would anyone even do the following...

foo(int inInt)
{
  Init(inInt);
}


How do you personally deal with this? Or do you just decide on a way you prefer and do it like that consistently.. Personally I normally put a lot of it into the constructor, but im guessing there's a reason not to do this? Cheers, J
vetroXL
vetroXL
I just follow one rule-of-thumb in my c++ code; I never call virtual functions in a constructor or destructor.

I virtualize the Init method... so basically I just initiailze member variables in the constructor, ie.e set them to null or whatever... and then implement a virtual Init or Create method that the client app calls, and do all the guts and glory in there...

class CMyClass : public CBase{   CMyClass():CBase(), someVar( NULL ), otherVar(2)   {   }   bool Create(){}};someFunc(){   CMyClass c;   c.Create();}


[Edited by - vetroXL on January 4, 2007 10:55:17 AM]
Bregma
Bregma
I have a simple rule of thumb: never use an init-style member function.

A well-designed class will have a class invariant. That is to say, some state that must always be valid while an object of that class exists. The job of the constructor is to obtain that state (or throw an exception if it's not possible to obtain the state), and the state lasts until the destructor is executed. It's possible that the invariant is contravened temporarily during the execution of a member function, but the invariant must either be restored by the end of that function or an exception must be thrown.

If you cannot determine the invariant of a class, it's poorly defined and you will end up in trouble later.

If you find you have more than one invariant, you are trying to do unrelated things in the class and need to refactor or you will end up in trouble later.

The alternative, that of having a partially-contructed object and finishing the constructor at a later time, is that every single access to the class must always check to make sure the object is valid (in other words, an explicit test for the class invariant must be performed on every operation). If you don't do that, you'll spend a lot of time wondering whty you get the weird crashes.

--smw
Stephen M. Webb
Professional Free Software Developer
ToohrVyk
ToohrVyk
I think this thread is discussion two orthogonal definitions of an "Init" method. The original post seems to discuss factoring code from a constructor int an Init method that is called from most constructors, while some replies discuss the possibility of a post-construction user-performed initialization call.

To answer both questions: it is expected that an object that has been constructed is fully usable. Requiring additional steps before the object can be used makes the object more difficult to manipulate and easier to get wrong (and also harder to verify). As for constructing objects, construction code is no different from any other code, and basic refactoring rules should be applied. If you find yourself doing the same thing over and over again in several constructors, factor the code and place it somewhere else (either an Init() function or a base class constructor).

Also, it is preferable to use initializer lists to manually assigning the members in the constructor.
gdunbar
gdunbar
Are you using exception handling as a rule? If not, you probably want to do an Init() method so you can return error codes if something fails. (I suppose you could return an error via a parameter in a constructor but that always looks funny to me).

Otherwise, doing everything in the constructor is feasible and probably produces better "looking" code, but Init() will work just fine too. I've done it both ways in various projects, and in the end I've found it doesn't matter that much.

Geoff
Zahlman
Zahlman
Don't do init() methods.

In rare cases, it makes sense to implement something that will put an existing object into an "as if just constructed" state. It makes sense to call such a member function "reset()". However, first consider if you can't just construct a temporary and assign it.

init() type things can make sense as a way of factoring out common code between constructors (because invoking one constructor from another DOES NOT do what you want; it instead constructs another temporary instance and promptly throws it away). However, it should then be *private*. And you *should* be doing as much of the work as possible with initializer lists anyway :P (Unfortunately, there is no good way to refactor duplication across two different initializer lists.)

Expecting the user to init() things manually is (a) error prone, because someone *will* forget eventually; (b) tedious, leaving the user wondering what's so great about C++'s new-fangled constructor doohickeys anyway; and (c) ugly, because it allows newly created objects - even when properly "inited" - to reside temporarily in an invalid state.

The thing is, doing it with init() is equivalent, in the procedural-code world, to doing things like:

int foo;foo = 2;


All throughout the code. And forgetting about initializer lists is like having "int foo = 0; foo = 2;" in the constructor (the "int foo = 0;" is generated implicitly; an initializer list lets you override the '0' value with '2').

If you won't accept the clear inferiority of doing things this way, then I really don't think I have any more to say to you.




Oh, and virtual functions don't enter into it, either. It is true that you can't safely call virtual functions from a constructor. That's fine; you shouldn't need to. Constructors *are not inherited*, and a base class' constructor is not responsible for any initialization of the children - only of the base.

The way to get constructors to be polymorphic - in the sense of using the right one for the derived class you are instantiating - is to write the base constructor as if there were no derived classes, and write the derived constructors so that they forward the appropriate arguments to the base constructor - in the initializer list. (By the time you get to the constructor body, it is *too late* to do anything about the base initialization.)

The way to get constructors to be polymorphic - in the sense of making a run-time decision as to which derived class to instantiate - is to use an abstract factory pattern. This works quite differently from what vetroXL illustrates.

class Base {  Base(int foo) {}};class D1 {  D1(int foo, int bar) : Base(foo) {} // shows the forwarding};class D2 {  D2(int foo, int bar) : Base(foo) {}};// A ridiculously simple abstract factory.std::auto_ptr<Base> create(int type, int foo, int bar) {  switch (type) {    case 0: return std::auto_ptr<Base>(new D1(foo, bar));    case 1: return std::auto_ptr<Base>(new D2(foo, bar));    default: assert(false);  }}


The concept of "virtual" simply doesn't have anything to do with constructors. You have to already have an object in order to do dynamic dispatch, and constructors are concerned with the process of giving you an object.

Topic Locked

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

Sign in to reply to this topic.