C++ / Default class instances

B7568a7d781a2ebebe3fa176215ae667
0
Wernaeh 101 Jun 10, 2007 at 17:40

Hello there :)

Just a short C++ code design question this time, namely how and where to generate default class instances.

Say I have a class that requires another class as a parameter storage, for example a class CWindow, which needs a CGuiSkin class to decide how to draw, what click regions to use and similiar.

The CGuiSkin class needs to be shared for several windows, and thus cannot be included in the CWindow class itself as a class member.
So, I added a CGuiSkin pointer to CWindow. When this pointer is Null, I want to use some default parameter set (so the user of my class need not fill a parameter storage if he doesn’t want to). In turn, I can then write a method CGuiSkin& CWindow::GetWindowSkin() which always returns a valid parameter storage (either the pointer target, or the default one).

Now, the question is where and how to implement this default parameter object.

The first idea was to make it a static class member of the CGuiSkin class, which works well for simple objects. However, it doesn’t work as well for more difficult classes (consider dynamic memory allocation in the constructor: the class instance is created even before my main() method, so there is basically no way to implement proper error handling)

The next idea was to implement the default instance as a singleton object, and create it the first time a CWindow has a Null pointer and still wants to retrieve a CGuiSkin object. The disadvantage here is object deconstruction, basically you’d just keep the object around until the OS (maybe) cleans it up after program shutdown, which also seems a bit hackish.

Last idea I had was to add a instance pointer to the CGuiSkin class similiar to the one used for a singleton implementation, but manually create / destroy the appended object at some place in program initialization and shutdown. The problem here is it’s not automated, and consequently hard to maintain.

I like the first method presented best, since it comes with minimal additional coding work.

Is there some design pattern for handling such default instances ?
Anybody out there got any better idea for the implementation of the described scheme ?

Thank you for your time,
Cheers,
- Wernaeh

11 Replies

Please log in or register to post a reply.

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Jun 10, 2007 at 19:53

I would go with making it a static member of CGuiSkin, but to solve the constructor-error-handling problem, make it do its initialization in a separate Init() method, which you can call from main() or someplace. Or alternatively, let the initialization be done in a static function of CGuiSkin, like CGuiSkin::InitDefaultInstance(), so you can use a regular constructor for the actual object but control when it is instantiated. You could even abstract this functionality into a base class template for convenience.

B7568a7d781a2ebebe3fa176215ae667
0
Wernaeh 101 Jun 10, 2007 at 20:49

You could even abstract this functionality into a base class template for convenience.

Templates sounds quite good, haven’t thought of these before! This at least keeps coding the accessor methods to a minimal workload, and I can add default instance functionality to every such config object by simply deriving.

The only hassle I still have is that I need to manually Init() and Destroy(). Yet I think I can’t overcome that, since with any kind of automated instancing, I automatically lose any order and thus any existing dependencies.

Well, I’ll try templates for now :)

Thank you for your help, it is greatly appreciated.

Cheers,
- Wernaeh

A8433b04cb41dd57113740b779f61acb
0
Reedbeta 167 Jun 11, 2007 at 07:05

@Wernaeh

The only hassle I still have is that I need to manually Init() and Destroy(). Yet I think I can’t overcome that, since with any kind of automated instancing, I automatically lose any order and thus any existing dependencies.

If you want, it’s probably possible to use some template metaprogramming tricks to arrange for every class in the program that has a default instance to be initialized with one call. It may not be worth it to do that, however. For dependency management, you could just make sure each object’s Init() first calls the Init() of anything else it depends on, and make sure multiple calls to Init() aren’t harmful - then the dependencies would manage themselves!

6b7e1a4b42e4b47d92fdef8bf2bd8e2c
0
Jare 101 Jun 11, 2007 at 08:12

The real problem usually is not initialization, but termination. And debugging when problems arise. Explicit initialization and termination lists may not be elegant, but in my opinion they are the most practical way to solve this problem. The kind of code you would add to (try to) make initialization and termination automatic (reference counting for example) can instead be used to debug problems.

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Jun 11, 2007 at 20:10

@Jare

The real problem usually is not initialization, but termination.

That is not a problem at all, just destroy in the reverse of initialization (and if you use function statics this is guaranteed).

6b7e1a4b42e4b47d92fdef8bf2bd8e2c
0
Jare 101 Jun 12, 2007 at 04:50

Oisyn, that’s a nice theory until the termination code uses systems that it didn’t use during initialization. Classic example (often associated with containers): setting pointers to NULL (no need for any external system) during initialization, but deleting (i.e. using the memory manager) them during termination. If I had a CDN$ for every programmer that has been bit by “automatic” ordering I would have ordered a bigger TV yesterday.

When initialization and termination is trivial, dependencies are very obvious, and you can easily see which “dummy” calls you need to add to ensure things work. In a large codebase things quickly get out of control, the dummy calls become undocumented dependencies, and it’s a mess.

Proper initialization and termination is a solvable problem, but it IS a problem. My design choice is still to make it explicit.

B7568a7d781a2ebebe3fa176215ae667
0
Wernaeh 101 Jun 13, 2007 at 07:45

Well actually, one could implement all three initialization methods (automatic, manual, and static allocation) as different template classes, so the programmer might then choose whatever kind of default instance is more suitable for a given class.

Simple parameter storages that just hold some ints, for example, might even live with static initialization, while more complicated ones could go with manual instancing and destruction.

I’m still working on the actual template, I’ll post it once it is in a more readable state.

Cheers,
- Wernaeh

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Jun 13, 2007 at 10:07

@Jare

Oisyn, that’s a nice theory until the termination code uses systems that it didn’t use during initialization.

Then it *should* use them during initialization :)

6b7e1a4b42e4b47d92fdef8bf2bd8e2c
0
Jare 101 Jun 14, 2007 at 06:38

@.oisyn

Then it *should* use them during initialization :)

class MyClass
{
  public:
    MyClass()
    {
      // Oisyn was here. :)
      delete new char;
    }
    // ...
  private:
    std::vector<int> mInts;
};

:)

340bf64ac6abda6e40f7e860279823cb
0
_oisyn 101 Jun 14, 2007 at 08:27

That’s bogus and you know it :)

class SomeSubSystem
{
private:
    SomeSubSystem()
    {
        SomeOtherSubSystem::getInstance(); // we don't need it know, but we need it during destruction
    }

    ~SomeSubSystem()
    {
        SomeOtherSubSystem::getInstance().doSomething(x);
    }
};

IMO, if you have spaghetti dependencies, you need to redesign your subsystems and their responsibilities.

In my experience, maintaining manual lists of init/deinit sequences is a hell. if a system Z is initialized after system A, and it turns out that at one point in time you need Z during initialization of A, you can’t just move Z before initialization of A, because Z is dependent systems D through G which also are constructed after A. But it’s not clear from the sequence what Z needs, and after you found out, you still don’t know what to move as well if you move D through G before A. If you do this automatically, you don’t have that hassle

6b7e1a4b42e4b47d92fdef8bf2bd8e2c
0
Jare 101 Jun 14, 2007 at 16:16

@.oisyn

That’s bogus and you know it :)

Yeah, I originally wrote something similar to your example, but what I finally posted was (a) funnier, and (b) illustrated the underlying point: that in a large system, dependencies can become hard to identify and maintain properly (especially when you include legacy and/or third party libraries). Your example shows the clean side of the problem; mine shows the WTF side.

My own experience is that the “automated” approach is great while it works; when it breaks, it is harder to find the problem. With an explicit list, if I suddenly decide that I want system X to also use system Y, I can go to the list and see if Y is initialized before X - if it isn’t, then I know I need to look carefully. With the automatic approach, I just do it and if I’m lucky then it is either ok or I get a crash. If I’m unlucky, it appears to work but a broken or circular dependency is hidden and may surface later when someone else makes a change in system Z. With half a dozen systems this is always manageable, but with the couple dozen of a large project it is not.@.oisyn

IMO, if you have spaghetti dependencies, you need to redesign your subsystems and their responsibilities.

That’s true regardless of which method you choose, isn’t it? After all, if your dependencies are controlled and documented, any method will work.