Shared subservient object

Viewed 75

I have a template class Foo that has a method, barMethod, that requires a Bar object. I could have each individual Foo object create Bar objects as needed, but I don't want to do that because it will be slow, and there is a good chance that different Foo objects could use the same Bar object.

Given that, I would like to have a BarManager object that has the existing set of Bar objects. The Foo objects could then ask it for Bar objects, and if an appropriate one already exists, the manager would simply return it. If it doesn't the manager would create a new one and return it.

It seems to me that there are two problems with implementing the "manager" approach.

  1. The manager would have to be a global variable, which I would prefer not to do.
  2. BarManager and Bar are both template classes, so even if I were to make the manager a global variable/object, I'm not sure how that would work. I guess I could make the global variable a void * and cast it whenever it is de-referenced to the appropriate templated class, but that seems really ugly.

There has to be a way to do this that isn't so ugly (perhaps using an auto pointer?). What is it?

EDIT: This software is part of a library that I (and perhaps others) will use later, which is why the classes are templated and the template type is not known a priori. Also, I want to keep things as simple as possible for the user of Foo, so I do not want the caller to have to do anything with BarManager or Bar (e.g. instantiating BarManager and giving the reference to each Foo).

3 Answers

If you:

  1. Know concrete type of BarManager in compile time.
  2. Don't want any user of Foo knowing about this internal mechanism.

Then you can have all Foo classes holds this BarManager as a static (definitely not a global var)

template <typename T>
class Bar{};

template <typename T>
class BarManager;

template <typename T>
class Foo
{
private:
    static BarManager<T>& GetBarManager()
    {
        static BarManager<T> managerInstance;
        return managerInstance;
    }

public:
    void barMethod()
    {
        auto& bar = GetBarManager().GetBarInstance();

        // Do something with `bar`
    }
};

template <typename T>
class BarManager
{
public:
    Bar<T>& GetBarInstance()
    {
        // Replace with cacher implementation:
        static Bar<T> dud;
        return dud; 
    }
};

int main()
{
    Foo<int> foo;
    foo.barMethod();

    return 0;
}

There are two alternatives on how to refer to BarManager:

  1. Have it as a global object / global pointer to a local object in main() / singleton / monostate. The advantage is easy access to it.
  2. Pass a pointer/reference to an instance of BarManager explicitly. The advantage is that you can have many BarManager objects. The disadvantage is having to pass the extra parameter to calls, especially if the call chains are deep and/or having to store a reference/pointer to it as a member variable.

So you'd need whatever was responsible for constructing Foos to first have constructed all BarManagers and then pass them into the Foo on construction. Since I don't really know the specifics I'll have to leave that to you, but given a simple: template<typename T> struct Bar { using value_type = T; const T _val; }; your BarManager skeleton could look like this:

template<typename T>
class BarManager {
    vector<shared_ptr<Bar<T>>> _bars;
public:
    using value_type = T;
    size_t size() { size(_bars); }
    shared_ptr<Bar<T>>& get_bar(const T target) {
        const auto it = find_if(begin(_bars), end(_bars), [&](const shared_ptr<Bar<T>>& i) { return i->_val == target; });

        if(it == end(_bars)) {
            _bars.push_back(make_shared<Bar<T>>(target));
            return _bars.back();
        } else {
            return *it;
        }
    }
};

From there your Foo can look like this:

template<typename T>
class Foo {
    shared_ptr<BarManager<T>> _my_manager;
    shared_ptr<Bar<T>> _my_bar;
public:
    using value_type = T;
    Foo(shared_ptr<BarManager<T>>& my_manager) : _my_manager(my_manager) {}
    void request_bar(const T& target) { _my_bar = _my_manager->get_bar(target); }
};
Related