Skip to main content

Having fun with Lazy Load

Posted in

Here is the problem I had to solve recently. I have objects, whose describing information is stored in a database. In my application I want to explore these objects, but I don't want them to be loaded at startup of my application. Mainly, because this causes a delay on application launch, especially when there are a considerable number of such objects. So I decided that this is the time for Lazy Load pattern to do its job.

Looking at my own perception and/or understanding of this pattern I start to think that the main problem of using Lazy Load is understanding it literally. By saying literally I mean the following (this code will explain it):

Object.h:

class Object
{
    public:
        int id() const;
        void setId(int iId);
 
        std::string name() const;
 
        // ...
 
    protected:
        bool load();
        // ...
 
    private:
        int mId;
        std::string mName;
        // ...
        bool mIsLoaded;
};

Object.cpp:

int Object::id() const
{
    return mId;
}
 
void Object::setId(int iId)
{
    mId = iId;
}
 
std::string Object::name() const
{
    if (!mIsLoaded)
        load();
 
    return mName;
}

I bet many people thought of adding a boolean flag to data members and checking it for false when an Object's getter is called. That what I did first, because everything seemed ok to me and I haven't noticed any problems... Until I ran make... and saw compiler errors staying that this is not right to modify object in a const method.
Hmm, looks like my expectations on getting this done in a short period of time are going to evaporate right now. So, what should we do?

  1. Don't make getters const.

    Well, I don't want getters to be not const. I mean I could do that, but I know that I will feel not comfortable. Getter is used to get data, it makes sense that it is const.

  2. Use const_cast.

    I'm not comfortable with this choice either. It is like cheating on C++ and telling lies to the compiler. It could have sound like "I promise I will not modify data here" but at the same time we are say that and do the opposite, we modify data. Shame on us if we go this way.

  3. Make data members mutable.

    I guess this way is the best out of all three. But, still not perfect and should not be used in every case. Bjarne Stroustrup says that declaring a member as mutable is mostly appropriate when only some of data members are supposed to be changed in const methods. If most of them are going to be modified, then it is highly recommended to put the modifiable data into a separate class.

Frankly speaking the last option was the closest to my case. I'm so lazy that I didn't want to create extra classes for separating modifiable data from actual objects (I had about 7 types of such objects), so I kept searching. Those who are searching will find the solution. Fortunately, I did, at least I think I did :). I started looking at Proxy pattern. But when I saw at its UML diagram the first my thought was "I still will have to create extra classes :(". I was getting used to the fact that lots of new classes are coming to my source code when I noticed the short phrase in a book saying that we should use C++ capabilities to implement Proxy and suddenly I thought about smart pointers (and further about operator overloading + templates).

What if I do this:

template<typename T>
class ObjectPtr
{
    public:
        ObjectPtr(int mId)
            : mId(0),
              mPointee(0)
        {
        }
 
        ~ObjectPtr()
        {
            // we are not obliged to check for mPointee to be equal to 0 since delete 0 is OK according to C++ standard.
            delete mPointee;
        }
 
        T* operator->()
        {
            if (0 == mPointee)
            {
                // create a real object here passing its id
            }
            return mPointee;
        }
 
        // ...
 
    private:
        int mId;
        T* mPointee;
        // ...
};

I liked this solution, and decided to stick with it. It seemed to me that the complete solution of my problem is here.

But here came another problem, what I did was making a template operator and trying to add a specialization for some types of objects:

template<typename T>
class ObjectPtr
{
    public:
        //...
        template<typename N>
        N* operator->()
        { }
};
 
template<>
template<>
Object1* ObjectPtr<Object1>::operator->()
{
    if (!mPointee)
        mPointee = myFunctionToCreateObject1();
    return mPointee;
}

And then I tried to use it:

ObjectPtr<Object1> p;
p->name();

.. but make said:

error: base operand of ‘->’ has non-pointer type ‘ObjectPtr’

I could not find out what was happening and what was causing the error. Honestly, at the moment of writing this I still don't know whether it is allowed to have template operators or not (I will investigate on this as soon as I finish this post), but I decided to move the logic to another internal template function and leave -> operator as a regular one.

template<typename T>
class ObjectPtr
{
    public:
        //...
        T* operator->()
        {
            return initialize<T>();
        }
 
        // ...
 
    private:
        template<typename N>
        N* initialize()
        {
            // by default I don't know what to do
        }
};
 
template<> // specialize class
template<> // specialize method
Object1* ObjectPtr<Object1>::initialize()
{
    if (!mPointee)
        mPointee = myFunctionToCreateObject1();
    return mPointee;
}

And finally it all worked as I wanted. Also note that in fact we started with implementing Lazy Load but finished with Lazy Initialization:).

Everything that I wrote here is only my vision on how to solve the given problem, so if you know a better solution feel free to share in comments.

Links:

Post new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
_rien_ship: