65.9K
CodeProject is changing. Read more.
Home

Using templates for initialization

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.50/5 (3 votes)

Nov 13, 2000

viewsIcon

156287

Use templates to initialize structures or simple member variables.

Introduction

It is quite common in coding to create a structure and then initialize it to all zeros via a memset() [or ZeroMemory()], and in the Microsoft world, it is also quite common to then set a member called cbSize to the sizeof() the structure. All this is very simple, but it can be a bit of a pain in the bum, as well as adding extra lines of code, which always is a bad thing!

Another issue is with simple class member initialization. It is not uncommon to have more than a single constructor. Forgetting to initialize variables in each has caused me problems more than once. Having an initialization function is one solution. I offer another solution.

Template initialization

With a few simple templates, we can rid ourselves of initialization problems!

template <typename T>
struct Clean : public T
{
    Clean()
    {
        ZeroMemory(this, sizeof(T));
    }
};

template <typename T>
struct Sized : public T
{
    Sized()
    {
        this->cbSize = sizeof(T);
    }
};

template <typename T>
struct CleanSized : public T
{
    CleanSized()
    {
        ZeroMemory(this, sizeof(T));
        this->cbSize = sizeof(T);
    }
};

Three templates are presented here to cover the various cases that you would encounter. Clean is if you want your structure to be just zero'd out. Sized is if you only want to set the cbSize member and CleanSized is if you want to do both.

Usage

Instead of specifying the structure directly, you just wrap it with the template. Such as the following:

    CleanSized<::SCROLLINFO> scroll_info;
    scroll_info.fMask = SIF_POS;
    scroll_info.nPos = 20;
    ::SetScrollInfo(hwnd, SB_VERT, &scroll_info, FALSE);

The first line creates the SCROLLINFO structure, but as it's wrapped by CleanSized, it will zero out the structure, as well as setting the cbSize value to the sizeof(SCROLLINFO). Simple.

You can also use this to express member variables in a class. I.e.:

class CSomeClass
{
    ...
private:
    CleanSized<::SCROLLINFO> m_scroll_info;
};

Then m_scroll_info will be initialized at the same time as the member initialization list (just before entering the constructor).

Now, this is pretty good, we can use this to clean structs in classes, but what about simple types such as int, float, enum, etc?

Well, we cannot use the above templates, as they require deriving from the template type, and as you cannot derive from simple types, this leads us to a new template:

template <typename T>
struct CleanSimple
{
    CleanSimple()
    {
        ZeroMemory(&m_t, sizeof(T));
    }

    T& AsType() {return m_t;}
    operator T&() {return m_t;}
    T* operator&() {return &m_t;}

private:
    CleanSimple(const CleanSimple& rhs);// disallow copy constructor
    operator=(const CleanSimple& rhs); // disallow operator=

    T m_t;
};

Now, you should be able to use this in the same way that you would use the simple type (see note below for an exception). Such as:

class CSomeOtherClass
{
public:
    void SomeFunction()
    {
        ++m_call_count;
        cout << "This function has been called " 
          << m_call_count << " times." << endl;
    }

private:
    CleanSimple<int> m_call_count;
};

(Note: Why the AsType() function? Well, I wish it wasn't there, and I'm not sure if it is a bug in the MS compiler or (as I know the much more likely answer is!) something I'm not thinking of, but in the case of:

CleanSimple<int> x;
int y;
y = x;

you get the following error:

error C2593: 'operator =' is ambiguous

Now, this is a bit strange as, if you change the code to:

CleanSimple<int> x;
unsigned int y;    // or float, short, or another type
y = x;

then it all works fine. Hmmm.)

OK, well this is all good and well, but this sets the variable to zero - what happens if we want to initialize it to some other value other than zero? Time for another template...

template <typename T, T t>
struct InitSimple
{
    InitSimple() : m_t(t) {}

    operator T&() {return m_t;}
    operator T&() const {return m_t;}

    T& AsType() {return m_t;}
    const T& AsType() const {return m_t;}

    T* operator&() {return &m_t;}
    const T* operator&() const {return &m_t;}

    T& operator=(const T& t) {return (m_t = t);}
    InitSimple(const InitSimple& rhs) {m_t = rhs.m_t;}

private:
    T m_t;
};

Now, this template allows you to initialize the type and value where you specify the variable. This means that you can specify in your class prototype, the default values of variables. This means that if you have multiple constructors, you don't have to remember to duplicate all the initialization for all the variables!! (This is a good thing.)

You use the class like this:

class CYetAnotherClass
{
private:
    InitSimple<int, 42> m_atltuae;
    ...
private:
    enum States
    {
        START, RUNNING, STOP
    };

    InitSimple<States, START> m_state;
};

(Note: Not that you would probably want to, but if you defined an InitSimple<> as const, then you MUST have a constructor, otherwise VC++ chokes as it doesn't think it can create a valid constructor because a const variable doesn't appear to have been initialized according to it! [I haven't bothered to check to see if this is mandated by the spec or not.])

Now I only came up with this stuff over the weekend (but who knows, I could have read an article a couple of years ago on this and this stuff finally bubbled to the top - but I don't think so !). So, there could be problems with it. Thus, if you find anything wrong, please give me a yell!