views:

131

answers:

2

Hi, I'll try and keep my sample code very straightforward, but it may have errors as I'm typing it on the spot.

I have a class named Phone.

class Phone
{
 public:
  Phone(std::string manufacturer, std::string model, std::vector<Feature> features);

 private:
  std::vector<Features> features;
  std::string model;
  std::string manufacturer;
};

I have a struct named Feature.

struct Feature
{
   Feature(std::string feature, std::string category);
   std::string feature;
   std::string category;
};

As you can see a phone has a list (vector) of features: ie. bluetooth, GPS, radio, etc, which have a category: networking, navigation, multimedia.

Now information about phones and features are stored in a sqlite3 database. I have a helper function which will retrieve a particular phone model from the database and return a populated Phone object. I also have a function which takes in a Phone object and writes the Phone to the database.

The problem is I need to provide the client some way of iterating over the list of Features of a Phone. For starters the database helper needs to know about the features so it can write them to the database. Second the client may need to retrieve a Phone from the database and then display the list of features to the user.

One solution is to add the following functions in class Phone

std::vector<Feature>::iterator begin()
std::vector<Feature>::iterator end()

This is not an ideal solution for me because the client code will not look intuitive - it will appear as if the client is iterating over a Phone, when they are actually iterating over Features.

Another solution is discussed in an article at http://accu.org/index.php/journals/1527 which discusses using a technique named "memberspaces" to expose iterators to client code. It would result in more readable client code, but the implementation is a bit messy in my opinion.

Is there a better solution to the iterator problem, or maybe there is a more suitable design pattern I could be using.

Any feedback would be greatly appreciated.

+1  A: 

In your case I would go for better names first:

typedef std::vector<Feature> Features;

Features::iterator features_begin();
Features::iterator features_end();
Features::const_iterator features_begin() const;
Features::const_iterator features_end() const;

Examples:
1)

 // Note: you'll need to define an operator<< for Feature
 // can be used with std::algorithms
 std::copy( phone.features_begin(), phone.features_end(),
   std::ostream_iterator<Feature>( std::cout, "\n\r" ) );     

2)

// Note: shamelessly borrowed from http://www.boost.org/doc/libs/1_44_0/doc/html/foreach/extensibility.html
// add overloads of range_begin() and range_end() for Phone::Features
inline Phone::Features::iterator range_begin( Phone& phone ){
   return phone.features_begin();
}

inline Phone::Features::iterator range_end( Phone& phone ){
   return phone.features_end();
}

namespace boost{
   // specialize range_mutable_iterator and range_const_iterator in namespace boost
   template<>
   struct range_mutable_iterator< Phone >{
      typedef Phone::Features::iterator type;
   };

   template<>
   struct range_const_iterator< Phone >{
      typedef Phone::Features::const_iterator type;
   };
}
...
// can be used with BOOST_FOREACH
BOOST_FOREACH( Feature x, phone ){
   std::cout << x << std::endl;
}

P.S. Given Jonannes' suggestion and the naming convention used by boost::range the names are now features_xxx() instead of xxx_features() (especially since they do make more sense in this context).

Eugen Constantin Dinca
Wouldn't that make the code incompatible with boost iterators?
Adam
Can you be more specific?
Eugen Constantin Dinca
@Adam the clang/llvm source code uses `features_begin()` and `features_end()` (and `features_size()`). I like that naming convention and adopted it in my projects.
Johannes Schaub - litb
I may be wrong but I was under the impression I couldn't use BOOST_FOREACH if my class didn't have a begin() and end() function.BOOST_FOREACH(Feature x, myPhone).So I would need to create a begin() and end() and call them in features_begin() and features_end()?
Adam
@Adam: with some modifications you can use `BOOST_FOREACH` with features_begin()/features_end(), see the updated answer.
Eugen Constantin Dinca
+1  A: 

If the client code is going to use BOOST_FOREACH, why not just add the method

const std::vector<Feature>& getFeatures() const;

to Phone?

Mattias Seeman
That is an option, but I think I'll either just use the something_begin() and something_end() convention, or use the memberspaces trick (if I really want to make the code compatible with boost_foreach). When I asked the question initially I thought there might have been some ultimate solution.
Adam