11

I'm attempting to create a generic container type to provide a single common interface, as well as hide the internal containers I'm using as they are subject to change.

Basically I have plugins that return collections of items and I don't wish the plugins to be aware of the type of container my code is using.

Can anyone point me in a better direction then the example code below?

template<class C, typename I>
class Container
{
 public:
 //...

    void push(const I& item)
    {
        if(typeid(C) == typeid(std::priority_queue<I>))
        {
           std::priority_queue<I>* container = (std::priority_queue<I>*)&_container;
           container->push(item);
        }
        if(typeid(C) == typeid(std::list<I>))
        {
           std::list<I>* container = (std::list<I>*)&_container;
           container->push_back(item);
        }
        else
        {
           //error!
        }
     };

  private:
     C _container;
 //...
}

Thanks

James McNellis
  • 348,265
  • 75
  • 913
  • 977
Ben Crowhurst
  • 8,204
  • 6
  • 48
  • 78
  • 4
    I can. Don't do that. Spend your time on designing your app so it uses statically typed containers. – Yakov Galka Dec 31 '10 at 07:07
  • 1
    I can point you in a better direction, sure: **don't attempt to do this**. Seriously. It won't gain you anything. Voice of experience speaking here. – Karl Knechtel Dec 31 '10 at 07:08
  • 8
    Why the downvote here? It's a perfectly legitimate question. Even if the answer is "don't do it this way", it was still a valid question. – Tim Dec 31 '10 at 07:13
  • 1
    I don't understand. I have an interface that is implemented by my plugins that requires a std::list to be returned. At a later date some requirement comes along to have these elements ordered for example. By hiding the type of container returned i will allow myself the flexibility to change without breaking existing plugins. Am i missing something here? – Ben Crowhurst Dec 31 '10 at 07:15
  • 4
    Just require that a sequence container is used; then you can use `list`, `deque`, or `vector` (or any other nonstandard container that meets the sequence container requirements) without any special handling at all. The entire point of the [container concepts](http://www.sgi.com/tech/stl/Container.html) is that you can easily write code that will work with any container that meets the requirements. (Yes, this means you won't be able to use non-containers like `priority_queue` directly, but it's highly unusual to need to do that anyway.) – James McNellis Dec 31 '10 at 07:19

3 Answers3

7

I have plugins that return collections of items and I don't wish the plugins to be aware of the type of container my code is using.

Have your plugins provide begin and end iterators into their collections of items and then consume the range as you see fit.

The greatest advantage of iterators is that they allow complete decoupling of how the data is stored (the container) from how the data is used (the algorithm; in your case, your application code that consumes the plugin data).

This way, you don't have to care how the plugins store their data and the plugins don't have to care what you do with their data once they give it to you.

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • Thanks for your answers James. This approach was looked into however became hairy when threads appeared on the scene. A DataStore plugin for example may have a get_events(int validator_id) method that can be called by multiple threads with varying validator ids. – Ben Crowhurst Dec 31 '10 at 07:36
  • 2
    @user: I don't understand; an iterator interface has no more concurrency issues than a container interface: either lock the container while you are iterating over it or make a copy of the container to iterate over. – James McNellis Dec 31 '10 at 19:35
  • +1. But I'm thinking it would be desirable to avoid the need to recompile the code that consumes the plugin's return values if a new plugin is created down the road that uses a different container type internally -- in which case, how? It seems you'd need to make the iterators returned by `begin()` and `end()` base classes (interfaces) in their own right, and give them pure virtual `operator++()` and `operator*()` etc. methods; then for each container type used, create a concrete subclass that forwards to the corresponding method for the right type of iterator, right? Seems tricky... – j_random_hacker Jan 01 '11 at 09:05
  • @j_random_hacker, notice the STL iterators don't implement a public interface? They use Duck Typing, you should be able to as well. http://en.wikipedia.org/wiki/Duck_typing (some day, maybe, they'll use concepts, but duck typing is the best we have for now). – Bret Kuhns Sep 12 '12 at 21:22
  • 2
    @BretKuhns: I think the fact that STL iterators are duck-typed is not the issue here. Suppose you define `vector x;` at file scope and a later function refers to `x.begin()` or `x.end()`: that function (the "consumer") will need to be recompiled if the definition of `x` is changed to `list x;`. Usually this isn't a big deal -- *unless* you're building a plugin system which needs to consume (iterate over) as-yet-unknown container types. In my comment, I was looking for a way to avoid having to recompile the consumer in this situation. – j_random_hacker Sep 13 '12 at 02:31
  • 1
    @j_random_hacker, OK I see your point now. You're right, runtime dispatching would be the solution to avoid recompiling. – Bret Kuhns Sep 13 '12 at 13:07
  • 1
    @BretKuhns: Today I randomly came across this more in-depth treatment of what I'm suggesting, in case you're interested: http://www.artima.com/cppsource/type_erasure.html. Good to know I'm not crazy :) – j_random_hacker Oct 04 '12 at 14:37
  • 1
    @j_random_hacker that was a great read, and surprisingly relevant to something I've been cooking up recently. Thanks for the follow-up! – Bret Kuhns Oct 05 '12 at 14:09
0

You know, when you wrote "common interface", I was sure you were going to show something Java style with an abstract class and subclasses that wrap standard containers. I was surprised to see a bunch of typeid calls...

But then, why do you want to do this? Most containers share a common interface, and with the power of SFINAE, you don't even have to write special code! Just use templates and call the method directly.

EDIT: Forgot that standard containers have no virtual methods and therefore, cannot be dynamic_casted.

Etienne de Martel
  • 34,692
  • 8
  • 91
  • 111
0

Start with a class as above, expose minimal interface needed by your plugins. Then implement it in terms of the container you are going to use. This is called container adapter and it is how std::stack is implemented.

If you really need adapter for more then one STL container go with template, have a look at std::stack, it will show how to do that.

Don't switch on typeid, why would you want that?

BTW, go with what James suggests unless there is a need to expose container itself.

Tomek
  • 4,554
  • 1
  • 19
  • 19