C++ Constructors vs. Init() Functions

Started by
43 comments, last by nobodynews 10 years, 1 month ago

I'm learning game programming at AIE US - I've reviewed a lot of source code while developing my projects, I've noticed that some people use either a constructor or a plan old void (or bool) Init() function to initialize their object's member variables.

Is there a good reason to use one or the other? I know constructors can use initialization lists which has a few benefits that I don't fully understand yet .

Thanks :]

Advertisement

The difference between them (obvious example inc!):


class MyClassCons
{
    public:
        MyClassCons()
        {
            Pointer* p = new Pointer();
        }
};

class MyClassInit
{
    public:
        MyClassInit();
        void Init()
        {
            Pointer* p = new Pointer();
        }
};

int main()
{
    MyClassConsc aClass;
    //p* already exists!

    MyClassInit bClass;
    //p* doesn't exist yet!
};

The problem with this is that sometimes p* could be a variable that depends on something ELSE to work.

Using a really bad example, say you put some OpenGL commands in your class' default constructor. Just by definining the class somewhere you'll get a runtime error because when the variable was created, right at the start of your program, the OpenGL context which is required before issuing glCommands wasn't active/didn't exist.

But you do could use Init(); since it would ONLY be called when you explicitly say so:


int main()
{
    MyClassInit aClass; //it's okay

    InitOpenGL();
    aClass.Init(); //okay, no runtime error
}

But you couldn't do the otherway around:


int main()
{
    InitOpenGL(); 

    MyClassCons aClass; //Still runtime error
}

Other than this situation (which I found to be sorta common), I think everything else is due preference...


The problem with this is that sometimes p* could be a variable that depends on something ELSE to work.

Especially if you have static objects form "MyClass". You cant really solve that by creating the objects in the right order.

Well, constructor's don't have any kind of return value, so, many people use an Init() type function that returns an error code of some sort.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)


Well, constructor's don't have any kind of return value, so, many people use an Init() type function that returns an error code of some sort.

The standard way would be to throw an exception from the constructor. (I dont do that either.)

A few other remarks on this choice:
- if you define your own constructor, the 'default' constructor, destructor and assignment operator are no longer applied from the compiler, meaning you need to write them yourself to (look up "the rule of three")
- when deciding this you should also think ahead if you want objects of the class to be reinitialized/ reused later and how you'll handle this (doing a own init function might need a cleanup partner :))

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

In C++, constructors are really important for RAII. Note that they are required if you wish to follow the objects-are-never-in-an-invalid-state thought process.

Personally, if I have to have confirmation that "initialization was a success", I'll add an IsInit or IsValid function. These cases are very, very rare in real life situations. Also... once you start using smart pointers it will be silly to explicitly Init after creating the object...

One setup I see often is having a private constructor and then some sort of static create function that returns a pointer to the class.

- if you define your own constructor, the 'default' constructor, destructor and assignment operator are no longer applied from the compiler, meaning you need to write them yourself to (look up "the rule of three")

Unless somebody made really weird changes to the language, that's simply not true. Creating your own constructor will never prevent default implementations of anything but the default constructor.

The "Rule of Three " doesn't say "you must implement all of them, because the compiler doesn't create them", it says "if you need one, you probably need all three". In fact, the big problem IS that the default implementations don't do everything they should in that case.

Obvious example:


class Demo()

{

Demo() : ptr(new Thing) {}

~Demo() { delete ptr; }

Thing* ptr;

}

And you probably don't want to know how often I'm seeing this kind of thing, always hand-waved away with "nah, it's never going to be copied anyway". Unless it suddenly is and "someone" (typically me) gets to spend hours debugging and tracking down the double delete introduced months ago.

If the compiler actually would stop creating defaults for assignment and _copy_ constructor (which is the one relevant to the Rule of Three) the code would have the decency to stop compiling. You'd also have an army of C++ coders lynching the person that came up with it.

Another common rule is that the constructor should only do minimal work to get the object in a valid state. Any heavy lifting that isn't absolutely needed would then go into an init() function.

f@dzhttp://festini.device-zero.de
This question has been coming up a lot lately, so sorry if I sound too repetitive for this answer.

RAII is about creating objects that are ready to use.

Being ready to use does NOT mean that it is hooked up to resources, that it has allocated blobs of memory, that it is attached to other objects, or otherwise integrated into the rest of the system.

When I create a std::vector I expect an empty object that is ready instantly that I can fill up at my convenience. When I create a std::ifstream I expect an object that is not yet attached to a file, but can be attached later, and I expect it to be instantly available, ready for use.

From another thread, when I create a network socket I expect an unconnected and empty stream to be available instantly, ready for use.

From another thread, when I create a logger I expect a fully-built logging class, but I expect it to come back instantly; it is initially connected to zero output streams and requires no resources, but is ready for use.

From another thread, when I create a generic game object I expect a fully built but completely empty game object, instantly available for use.

Going back through each:

When I create a vector there might be an optional constructor that does more work, but it isn't strictly necessary; by default I instantly get an empty object that is fully initialized to empty.

When I create a network socket there might be an optional constructor that attaches to the server and does some work, but it isn't strictly necessary; by default I instantly get an object that is fully initialized to a disconnected state.

When I create a logger there might be an optional constructor that accepts existing listening streams and hooks them up, but it isn't strictly necessary; by default I instantly get an object that is fully initialized to an empty listener state.

When I create a specific game object or an array of game objects, I absolutely DO NOT WANT each of those constructors to spawn off calls to load models from disk and into the cache system, to load textures from disk and into the cache, to load animations from disk, to load audio from disk, to integrate all of them into the rendering engine, to position them in the spatial trees, and so on. If I created an array of 50 game objects I might be looking at multiple seconds before the constructor returns to me, and it is doing an awful lot more than just constructing an empty object immediately. By default I instantly get an object that is fully initialized to an empty state.

Object lifetimes are generally more complex than a simple creation and destruction process. Often there is a fully constructed empty state (the default constructor) that is available instantly. There are often various levels of connectedness within the system. Maybe an SQL connection has states of disconnected, waiting for connection to the server, logging in, waiting for credentials, transmitting commands, waiting for results, and busy with communications. Maybe your game object has empty, proxied and out of world, proxied and in world, loaded out of world, loaded in world, active, disabled, in use, and assorted other states.

RAII means to initialize things to be ready to use. "Empty" and "Disconnected" are perfectly valid definitions of ready to use, and for non-trivial objects are usually the best default.

This topic is closed to new replies.

Advertisement