design opinions requested

T

Tim H

I have a design opinion. I have 3 models in mind, and I don't
particularly like them, so maybe someone can suggest or help justify.

I have a set of essentially unrelated classes (about 6).

class Foo;
class Bar;
class Bat;

I have a container-ish class that holds many of those other classes
and other meta-data:

class Stuff {
public:
vector<pair<string, Foo *> > foos;
vector<pair<string, Bar *> > bars;
vector<pair<string Bat *> > bats;
}

So here's the problem. I need to store the unrelated classes in a
somewhat related way. I need to sort them all together in insert
order, for later retrieval. What I mean is that later, I need to walk
the container somehow and pull out al the Foos, Bars, and Bats,
intermingled with each other, in the order they were inserted.

So, I have dreamed up three answers, none of which I really like.

1) Make a generic base class, Stuff_Item, which is a base class for
Foo, Bar, Bat, etc. Store all Foo, Bar, Bat in a single vector.
Stuff_Item::stuff_type() returns and enum that callers use to
determine the type of the thing held. Callers then up-cast.

2) Make a generic base class, Stuff_Item, which is not a base class,
and which has methods stuff_type() and stuff_index(). stuff_type
returns an enum that callers use to determine the type of the thing
held. stuff_index returns an index (or an iterator) into a type-
specific vector. Keep a seperate vector of Stuff_Items. Callers can
then use that as a (database-style) index.

3) Make a Stuff::Item inner class, which holds an enum of pointers to
Foo, Bar, Bat.. That inner class exposes is_foo(), is_bar(), is_bat()
methods, and get_foo(), get_bar(), get_bat() methods. Just store one
vector of Stuff::Item.


Thoughs, justifications, other ideas?

Thanks.

Tim
 
E

edd

I have a design opinion. I have 3 models in mind, and I don't
particularly like them, so maybe someone can suggest or help justify.

I have a set of essentially unrelated classes (about 6).

class Foo;
class Bar;
class Bat;

I have a container-ish class that holds many of those other classes
and other meta-data:

class Stuff {
public:
vector<pair<string, Foo *> > foos;
vector<pair<string, Bar *> > bars;
vector<pair<string Bat *> > bats;
}

So here's the problem. I need to store the unrelated classes in a
somewhat related way. I need to sort them all together in insert
order, for later retrieval. What I mean is that later, I need to walk
the container somehow and pull out al the Foos, Bars, and Bats,
intermingled with each other, in the order they were inserted.

Thoughs, justifications, other ideas?

How about this?:

class Stuff
{
public:
typedef boost::variant
<
std::pair<std::string, Foo *>,
std::pair said:
value_type;

// ...

private:
std::vector<value_type> mixed_;
};

Using this approach you can:

- provide a view of the sequence that only sees pair<string, Foo *>,
for example, using boost::filter_iterator in combination with the
boost::get() overloads for variants.
- iterate over all elements and perform a custom action depending on
the type of object in each variant using boost::static_visitor

I think you'll also avoid the (possibly negligible) overhead
associated with virtual function calls this way, too, though I didn't
check.

http://www.boost.org/doc/html/variant.html

Kind regards,

Edd
 
D

Daniel T.

Tim H said:
I have a design opinion. I have 3 models in mind, and I don't
particularly like them, so maybe someone can suggest or help justify.

I have a set of essentially unrelated classes (about 6).

class Foo;
class Bar;
class Bat;

I have a container-ish class that holds many of those other classes
and other meta-data:

class Stuff {
public:
vector<pair<string, Foo *> > foos;
vector<pair<string, Bar *> > bars;
vector<pair<string Bat *> > bats;
}

So here's the problem. I need to store the unrelated classes in a
somewhat related way. I need to sort them all together in insert
order, for later retrieval. What I mean is that later, I need to walk
the container somehow and pull out al the Foos, Bars, and Bats,
intermingled with each other, in the order they were inserted.

So, I have dreamed up three answers, none of which I really like.

1) Make a generic base class, Stuff_Item, which is a base class for
Foo, Bar, Bat, etc. Store all Foo, Bar, Bat in a single vector.
Stuff_Item::stuff_type() returns and enum that callers use to
determine the type of the thing held. Callers then up-cast.

2) Make a generic base class, Stuff_Item, which is not a base class,
and which has methods stuff_type() and stuff_index(). stuff_type
returns an enum that callers use to determine the type of the thing
held. stuff_index returns an index (or an iterator) into a type-
specific vector. Keep a seperate vector of Stuff_Items. Callers can
then use that as a (database-style) index.

3) Make a Stuff::Item inner class, which holds an enum of pointers to
Foo, Bar, Bat.. That inner class exposes is_foo(), is_bar(), is_bat()
methods, and get_foo(), get_bar(), get_bat() methods. Just store one
vector of Stuff::Item.


Thoughs, justifications, other ideas?

I'd have to see how the Stuff class is used to figure the best
solution...
 
T

Tim H

I'd have to see how the Stuff class is used to figure the best
solution...

Most of the time Stuff will be handled in one of two patterns.

for each item in stuff.big_list {
if item is_foo()
handle_foo()
else if item is_bar()
handle_bar()
else
handle_bat()
}

or

for each item in stuff.big_list {
if item is_foo()
handle_foo
}

To make matters more complicated, there are actually three different
Stuff containers, and each can hold a subset of the total things. For
example a Stuff container can hold Foo, Bar, or Bat. A Junk container
can hold Bar or Bat, but not Foo. A Mess container can hold Foo or
Bat, but not bar.

This extra complication is what steers me away from choices 1 and 2.
The Stuff_Item class has to know about all the various things that can
be stored in all the various containers. Why should an Item that goes
into a Junk container know anything about Foo - Foo is not valid
inside a Junk container! On th eother hand, my choice #3 ends up
repeating boilerplate code in each container's inner class.

Once these containers are built up, they are not going to change very
often. They are going to be read fairly often.
 
D

Daniel T.

Tim H said:
Most of the time Stuff will be handled in one of two patterns.

for each item in stuff.big_list {
if item is_foo()
handle_foo()
else if item is_bar()
handle_bar()
else
handle_bat()
}

or

for each item in stuff.big_list {
if item is_foo()
handle_foo
}

The above looks like two perfect candidates for polymorphism.
To make matters more complicated, there are actually three different
Stuff containers, and each can hold a subset of the total things. For
example a Stuff container can hold Foo, Bar, or Bat. A Junk container
can hold Bar or Bat, but not Foo. A Mess container can hold Foo or
Bat, but not bar.

So you might end up with several different contexts in which to use
"stuff". Each context should be a pure virtual class. Foo, Bar and Bat
will all derive from StuffContext. Bar, and Bat will derive from
JunkContext. Foo and Bat will derive from MessContext.

class StuffContext {
public:
virtual void handleStuff() = 0;
};

class Stuff {
typedef vector< pair< string, StuffContext* > > Container;
Container stuff;
public:
void handle() {
for ( Container::iterator it = stuff.begin();
it != stuff.end();
++it )
{
it->second->handleStuff();
}
}
};
 
T

Tim H

The above looks like two perfect candidates for polymorphism.


So you might end up with several different contexts in which to use
"stuff". Each context should be a pure virtual class. Foo, Bar and Bat
will all derive from StuffContext. Bar, and Bat will derive from
JunkContext. Foo and Bat will derive from MessContext.

class StuffContext {
public:
virtual void handleStuff() = 0;

};

class Stuff {
typedef vector< pair< string, StuffContext* > > Container;
Container stuff;
public:
void handle() {
for ( Container::iterator it = stuff.begin();
it != stuff.end();
++it )
{
it->second->handleStuff();
}
}

};

Two problems with this. First, handle_stuff() is up to the caller.
Sometimes they just want to print a Foo, sometimes they want to do
other things with it. I can't predict nor wrap that. Removing the
base class method, I arrive at something similar to my #1. The second
problem is that this exports policy (what can/can't be contained) from
the container into the containee.

I know I am being a bit difficult, I'm just trying to work through all
the pros and cons :)

Thanks

Tim
 
E

edd

Most of the time Stuff will be handled in one of two patterns.

for each item in stuff.big_list {
if item is_foo()
handle_foo()
else if item is_bar()
handle_bar()
else
handle_bat()
}

This type switching is nasty and should be avoided. This is what the
static_visitor facility of boost::variant is for. If you add any extra
types to the variant that aren't supported by your static_visitors,
you'll get a compiler error, rather than run-time code failing in some
strange way. And if you remove types the static_visitor will still
work just the same.
for each item in stuff.big_list {
if item is_foo()
handle_foo
}

The variant/filter_iterator solution handles these cases nicely. You
won't even have to do explicit if-tests. That will be handled by the
filter_iterator automatically.

You'd just have some code like:

StuffView<Foo> v(stuff);
for (StuffView<Foo>::iterator it = v.begin(), e = v.end(); it != e; +
+i)
{
// *it would yield a pair<string, Foo *>,
//or just a Foo* if that's what's needed
}
To make matters more complicated, there are actually three different
Stuff containers, and each can hold a subset of the total things. For
example a Stuff container can hold Foo, Bar, or Bat. A Junk container
can hold Bar or Bat, but not Foo. A Mess container can hold Foo or
Bat, but not bar.

This extra complication is what steers me away from choices 1 and 2.
The Stuff_Item class has to know about all the various things that can
be stored in all the various containers. Why should an Item that goes
into a Junk container know anything about Foo - Foo is not valid
inside a Junk container! On th eother hand, my choice #3 ends up
repeating boilerplate code in each container's inner class.

This adds no extra complication to the solution I presented. You can
have a static_visitor work with a number of different variants.
Additional overloads of the inner operator()s will simply be
"ignored".

CustomStuffVisitor v;
std::for_each(stuff.begin(), stuff.end(), boost::apply_visitor(v));

Fits like a glove.

Edd
 
D

Daniel T.

Two problems with this. First, handle_stuff() is up to the caller.
Sometimes they just want to print a Foo, sometimes they want to do
other things with it.

Each caller creates a different context. I.E., you need a different
interface for each context. Previously, I assumed that Stuff, Junk and
Mess were the contexts but I may have been wrong. Maybe they are simple
containers. Whatever the context is, that creates an interface.
I can't predict nor wrap that.

You, of course, can predict what your code does... or at least what it
*should* do. :)
The second problem is that this exports policy (what can/can't
be contained) from the container into the containee.

If's a simple container, then Stuff probably need not exist. Just use of
vector that holds objects of the approprate context.
I know I am being a bit difficult, I'm just trying to work through all
the pros and cons :)

No problem. It is hard to work through all the pros and cons when I only
have partial information. However, somewhere in your code, you have:

for each item in stuff.big_list {
if item is_foo()
handle_foo()
else if item is_bar()
handle_bar()
else
handle_bat()
}

The above should be:

for each item in stuff.big_list {
item.handle();
}
 
T

Tim H

This type switching is nasty and should be avoided. This is what the
static_visitor facility of boost::variant is for. If you add any extra
types to the variant that aren't supported by your static_visitors,
you'll get a compiler error, rather than run-time code failing in some
strange way. And if you remove types the static_visitor will still
work just the same.


The variant/filter_iterator solution handles these cases nicely. You
won't even have to do explicit if-tests. That will be handled by the
filter_iterator automatically.

You'd just have some code like:

StuffView<Foo> v(stuff);
for (StuffView<Foo>::iterator it = v.begin(), e = v.end(); it != e; +
+i)
{
// *it would yield a pair<string, Foo *>,
//or just a Foo* if that's what's needed

}


This adds no extra complication to the solution I presented. You can
have a static_visitor work with a number of different variants.
Additional overloads of the inner operator()s will simply be
"ignored".

CustomStuffVisitor v;
std::for_each(stuff.begin(), stuff.end(), boost::apply_visitor(v));

Fits like a glove.

Edd

I'm going to look more into this.

Thanks
 
T

Tim H

Each caller creates a different context. I.E., you need a different
interface for each context. Previously, I assumed that Stuff, Junk and
Mess were the contexts but I may have been wrong. Maybe they are simple
containers. Whatever the context is, that creates an interface.


You, of course, can predict what your code does... or at least what it
*should* do. :)

Sorry, this is a linkable library that other code will use. I can't
predict :)


The "containers" I have been speaking of are not as simple as I am
saying, though. They actually represent nodes in a tree of a larger
data structure. In fact, one of the things that these containers can
contain is other containers.
No problem. It is hard to work through all the pros and cons when I only
have partial information. However, somewhere in your code, you have:

for each item in stuff.big_list {
if item is_foo()
handle_foo()
else if item is_bar()
handle_bar()
else
handle_bat()
}

Somewhere, that code exists, but the handle_* are undefined from my
POV.
The above should be:

for each item in stuff.big_list {
item.handle();
}

There is no common interface, except maybe <<, on these underlying
objects. Handle() is a place-holder only for this example. :)

Tim
 
T

Taran

Sorry, this is a linkable library that other code will use. I can't
predict :)

The "containers" I have been speaking of are not as simple as I am
saying, though. They actually represent nodes in a tree of a larger
data structure. In fact, one of the things that these containers can
contain is other containers.




Somewhere, that code exists, but the handle_* are undefined from my
POV.



There is no common interface, except maybe <<, on these underlying
objects. Handle() is a place-holder only for this example. :)

Tim

In the class Stuff have a mapping (not std::map) which has three
fields
- Field one which is a counter incremented for every insert.
- Field two which is an enum or int whatever you want to indicate
where this stuff was added to Foo, Bar or Bat.
- Field three, the index of the vector at which the the relevant Foo,
bar bat was added.

- Give the Stuff an interface insert(Foo*),insert(Bar*),insert(Bat*)
where you increment the counter, fill in the second field for where to
add Foo, Bar or Bat, and the third field the index of the vector where
adding.

- As for the void* get(which_type), just index the map, find the
first added thing, find which thing and retrieve from the relevant
vector at the vector index given by the third field of the mapping.

Do an reinterpret_cast'ing depending on which_type.

Haven't tried this and cannot gaurantee it will for sure certainly
work. But at the face looks fine given that memory is not at premium
for you.

HTH.
Regards,
Taran
 
D

Daniel T.

Tim H said:
Somewhere, that code exists, but the handle_* are undefined from my
POV.

Does the above block of code exist in your library, or is it how you
want users to use your library?
 
T

Tim H

Does the above block of code exist in your library, or is it how you
want users to use your library?

It's one way to use this aspect of the library (the tree of data
structs). This tree of stuff is produced once, or periodically, from
a running system. Users of the library use the tree to analyze
stuff. Some users want to just dump all the stuff to a file. Some
users want to look for patterns. Some users want to extract parts of
the stuff and turn it into other gunk.

Sorry to be vague. I doubt very much anyone cares the details :)
 
D

Daniel T.

Tim H said:
It's one way to use this aspect of the library (the tree of data
structs). This tree of stuff is produced once, or periodically, from
a running system. Users of the library use the tree to analyze
stuff. Some users want to just dump all the stuff to a file. Some
users want to look for patterns. Some users want to extract parts of
the stuff and turn it into other gunk.

Sorry to be vague. I doubt very much anyone cares the details :)

I'm not sure why you were so vague to such a simple question though.
Surely you know what you have written (or intend to write.) Is something
like the above block of code in your library? Surely you know the answer
to that...
 
T

Tim H

I'm not sure why you were so vague to such a simple question though.
Surely you know what you have written (or intend to write.) Is something
like the above block of code in your library? Surely you know the answer
to that...


Yes, of course. That block exists in my own users of teh library.
But in each case "handle_foo()" is not exactly the same. Sometimes I
just print out "Found a Foo: name = ". Sometimes I check that some
sub-portion of foo works as expected. Sometimes I pass the foo to
another funtion.
 
D

Daniel T.

Tim H said:
Yes, of course. That block exists in my own users of teh library.
But in each case "handle_foo()" is not exactly the same.

Then your library follows more of a representational paradigm rather
than an OO paradigm so the Visitor pattern is appropriate. You might
also want to check out the Acyclic Visitor pattern.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top