1

I'm making a class for lazy initialization of a bunch of objects (not totally generic, all under my control). Only one object of each type will exist. I have a linear-time implementation using std::vector and boost::any already. Hopefully it should give a better idea of what I'm talking about.

I can make the assumption that all the objects I'll want to access have

typedef boost::shared_ptr<CLASSNAME> HandleType

in their definitions and they all have a constructor that takes the ObjectProvider by reference.

class ObjectProvider {
    typedef std::vector<boost::any> ContainerType;
    ObjectProvider() : container() {}

public:

    // this is where the relevant method is
    template <class TElementType>
    typename TElementType::HandleType get() {
        for (ContainerType::iterator it = container.begin(); it != container.end(); ++it) {
            try {
                return boost::any_cast<typename TElementType::HandleType>(*it);
            } catch (boost::bad_any_cast &) {}
        }
        // failed to find it so create it
        TElementType::HandleType handle = boost::make_shared<TElementType>(*this);
        container.push_back(handle);
        return handle;            
    }

private:
    ContainerType container;
};


// ----- FOR TESTING -----
class SomeObject {
public:
     SomeObject(ObjectProvider &) {}
     typedef boost::shared_ptr<SomeObject> HandleType;
};

int main(int argc, char** argv) {
    ObjectProvider provider;

    // expensive creation is done here
    SomeObject::HandleType obj1 = provider.get<SomeObject>();

    // expensive creation is not re-done
    SomeObject::HandleType obj2 = provider.get<SomeObject>();
    assert (obj1 == obj2); // pointers should point to the same object

}

Some motivation: Many of these objects (they're clients for various services) require creating additional clients of various types, but I don't want to be recreating them each time. So the goal of this class is to provide a method of caching the already-created clients.

Here is my question:

  • Is there a better way to do this?

  • In particular, is there a way to avoid the loop in get<...>() and somehow key by type? I'd love to have constant-time access instead of linear time and the current method fails to make use of the type information available for lookup.

Just for a little additional explanation of what I'm thinking, I might do something like this in Java:

Map<Class<?>, Object> objMap;
public <T> T get(Class<T> class) {
    Object obj = objMap.get(class);
    if (obj != null) {
        return (T)obj;
    } else {
        T newObj = ;// fiddle with java reflection to create a new instance
        objMap.put(class, newObj);
    }
 }
chessbot
  • 436
  • 2
  • 11
  • Is this a closed set of types, or open? In other words, when definining the datastructure that holds the handles, do you know all the possible types that it should hold? – dhavenith Aug 27 '13 at 19:03
  • @dhavenith At what time? It's closed at compile-time in general but I'd prefer not to hard-code in the list of clients, if that's what you're getting at: it would introduce circular dependency problems I would rather avoid. – chessbot Aug 27 '13 at 19:07

4 Answers4

1

If there is only one of each type, then you can use typeid to extract a string representing the type, and use that as a key to a map or unordered_map.

    //...
    typedef std::map<std::string, boost::any> ContainerType;
    //...

    template <class TElementType>
    typename TElementType::HandleType get() {
        std::string name = typeid(TElementType).name();

        ContainerType::iterator it = container.find(name);
        if (it != container.end()) {
            try {
                return boost::any_cast<typename TElementType::HandleType>(it->second);
            } catch (boost::bad_any_cast &) {}
        }
        // failed to find it so create it
        TElementType::HandleType handle = boost::make_shared<TElementType>(*this);
        container[name] = handle;
        return handle;            
    }
jxh
  • 69,070
  • 8
  • 110
  • 193
  • Trying this out... Any idea on how widely `typeid` is supported? – chessbot Aug 27 '13 at 19:14
  • `typeid` is described in the C++ standard. You need to `#include `. – jxh Aug 27 '13 at 19:15
  • There is likely a more clever answer that is based on how C++.11's tuple works. I don't have time to hash it all out now, but I'll work on it later. Until then, think about how you might write a template function to iterate over a boost::tuple to look for the right member. – jxh Aug 27 '13 at 19:50
0

If you want a class to be instanciate only once, you are probably looking for the design pattern singleton.

Wikipedia definition:

In software engineering, the singleton pattern is a design pattern that restricts the Instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.

Other links about singleton

C++ Singleton design pattern

Singleton: How should it be used

Can any one provide me a sample of Singleton in c++?

And you can find many more explanations on the internet about it.

Community
  • 1
  • 1
Zerkan
  • 51
  • 4
  • Yes, I'm aware that this is similar to the Singleton pattern. However, identifyig that pattern doesn't actually tell me how to implement it! – chessbot Aug 27 '13 at 18:58
  • I gave you few links related to it implementation. Then you no longer need collection you will juste have to do MyType::instance() to get the object and you are sure that it is only instanciate once. – Zerkan Aug 27 '13 at 19:03
  • I don't want to use a static initializer: I might have multiple instances of the object floating around, but within a certain conceptual barrier only one instance exists. For example, the clients are not thread-safe so one ObjectProvider would exist per thread. – chessbot Aug 27 '13 at 19:13
  • Ok then I think the idea of map and typeid (which is standard C++98/11 so should be available on all compilers) is one of the best option – Zerkan Aug 27 '13 at 19:20
0

If you are concerned about typeid() support, you can also add a class index yourself.

class SomeObject {
public:
    SomeObject(ObjectProvider &) {}
    typedef boost::shared_ptr<SomeObject> HandleType;
    static const int ClassIndex = 0;
};

and the object provider becomes

class ObjectProvider {
    typedef std::vector<boost::any> ContainerType;

public:
    ObjectProvider() : container() {}

    // this is where the relevant method is
    template <class TElementType>
    typename TElementType::HandleType get() {
        int idx = TElementType::ClassIndex;
        if (container.size() <= idx) {
            container.resize(idx + 1);
        }

        // Check if object exists
        if (container[idx].empty()) {
            typename TElementType::HandleType handle = boost::make_shared<TElementType>(*this);
            container[idx] = handle;
        }

        return boost::any_cast<typename TElementType::HandleType>(container[idx]);
    }

private:
    ContainerType container;
};

On the downside, you must make sure that different classes don't clash on the ClassIndex field.

umlum
  • 1,107
  • 6
  • 11
0

If you want constant time access, you could create your own type-id:

class Registry
{
private:
    static int get_id()
    {
        static int id = 0;
        return id++;
    }

    template< typename T>
    static int get_type_id()
    {
        const static int id = get_id();
        return id;
    }
    std::vector<HandleType> handles;
    HandleType make_handle( const Registry&r)
    {
        // do what you need to do to create a new handle.
    }
public:
    template<typename T>
    HandleType get()
    {
        int id = get_type_id<T>();
        if (id + 1 > handles.size())        
        {
            handles.resize( id + 1, HandleType());
        }
        if (!handles[id])
        {
            handles[id] = make_handle( *this);
        }
        return handles[id];
    }

};

EDIT: I see that I've essentially given the same answer as umlum, except that this answer also tries to automatically create a numerical id for each type.

dhavenith
  • 2,028
  • 13
  • 14