views:

232

answers:

9

I'm just learning C++ and programming. I'm creating a class called Distance. I want to allow the user (programmer using), my class the ability to convert distances from one unit of measure to another. For example: inches -> centimeters, miles -> kilometers, etc...

My problem is that I want to have one method called ConvertTo that will convert to any unit of measure.

Here's what I have so far:

// unit_of_measure is an enum containg all my supported lengths,
// (eg. inches, centimeters, etc...)
int Distance::ConvertTo(unit_of_measure convert_unit)
{
 switch (convert_unit)
 {
  case inches:
   if (unit != inches) {
    if (unit == centimeters) {
     distance *= CM_TO_IN;
     unit = inches;
     return 0;
    } else {
     cerr << "Conversion not possible (yet)." << endl;
     return 1;
    }
   } else {
    cout << "Warning: Trying to convert inches to inches." << endl;
    return 2;
   }
  case centimeters:
   if (unit != centimeters) {
    if (unit == inches) {
     distance /= CM_TO_IN;
     unit = centimeters;
     return 0;
    } else {
     cerr << "Conversion not possible (yet)." << endl;
     return 1;
    }
   } else {
    cout << "Warning: Trying to convert inches to inches." << endl;
    return 2;
   }
// I haven't written anything past here yet because it seems
// like a bad idea to keep going with this huge switch 
// statement.
  default:
   cerr << "Undefined conversion unit." << endl;
   return -1;
 }
}

So what do I do? Should I break this up or just continue on with what will become a HUGE switch statement.

+7  A: 

Break it up into functions. What you have there is going to be very difficult to maintain and to use. It would be more convenient to the user and the programmer to have functions with descriptive names like:

double inchesToCentimeters(double inches);
double centimetersToInches(double cent);

The function names tell you exactly what function to call, and there's no need to pass in the extra parameter that keeps track of the units.

Just as an aside, in order to prevent having to keep track of what units a measurement is in, it's a good practice to always store you numbers in a common unit everywhere in your program, then convert to the display units only when you need to. For example, a program I'm maintaining now keeps all distance values in meters, but can convert to just about any distance unit you could think of.

When you use a common unit, you save yourself a lot of function writing. Say your common distance unit is meters, now once you write the functions converting from meters to every other unit you need, and from all the other units to meters, you can combine these to go from any unit - to meters - to any other unit.

Bill the Lizard
But I also want to have conversions like `inchesToMiles(double inches);`. Is this method still OK? I'll probably end up with like 50 methods in total (not counting my `toString()` method, constructors, overloaded operators, etc.).
Bob Dylan
It's not ok. Using "double" instead of tagged or separate static wrapped-double types is potentially disastrous, as you can easily misuse inches where you have centimeters, etc.
wrang-wrang
Yes, that's fine. It's not uncommon to have dozens or hundreds of methods in these types of classes. One thing that will help to keep things straight is to have one class for distance conversions, one for volume conversions (which might *use* the distance class), one for temperature conversions... and so on.
Bill the Lizard
@Bill: Thanks. Just making sure.
Bob Dylan
That sounds exhausting. I'm glad I don't have to program in C++
Breton
@Breton: Well, there *are* libraries available. :) It looks like Bob is doing this just as a learning exercise.
Bill the Lizard
Well I guess I'm just confused as to why you'd have a thousand methods to memorize (and maintain!) instead of just a single method that uses a table of constants. Perhaps your thousand methods don't really exist, but are virtual methods that really just call a single method (single point of failure) (I don't know C++ so I don't know if that's possible)
Breton
@Breton: You could do it that way in C++. However, there's really nothing to memorize if all of your methods follow the same "inchesToMeters" style naming convention. Also, I'd think it would be the same effort, if not easier, to maintain those methods than it would be to maintain a table of conversion factors.
Bill the Lizard
On that last point, I will have to strongly disagree. However I'm at a loss as to how to argue the point because it seems obscenely obvious to me that editing constants in a sea of programming code is much more difficult than editing an external (CSV perhaps) file that non experts can understand.
Breton
Also if there's a thousand methods, there's physically more code to maintain. What if you made a mistake in just one of those methods? How would you ever find it? What if you made a mistake in *how* you were calculating the conversion? Perhaps you need to switch to using some kind of arbitrary precision library? You'd need to correct each and every one of those hundred/thousand methods. How is that easier? Or have I got it wrong? Is there some magic I don't understand?
Breton
@Breton: Well, first editing constants in a function would be easier in my mind, since you'd know by the function names which ones to change. It would be a lot easier to make a mistake editing a table of numbers. Next, you'd test each method individually, so finding any mistakes shouldn't be an issue. Last, changing *all* of the functions to a different design is a valid argument. You'd definitely want to make sure you have the right design up front.
Bill the Lizard
Actually, you'd best use templates for this: `convert<inches>::to<miles>(5.0)`. This would allow you to have a single template specialization table behind the scenes: `const double inches::in_meters = 0.0254;`
MSalters
+4  A: 

My approach would be to always store the distance in the same unit. This avoids you always having to double-check your units when you need to convert the value.

A skeleton of the code might look something like this:

class Distance
{
public:

   float ConvertTo (unit_of_measure convert_unit)
   {
      return (_distanceInInches * getConversionFactor(convert_unit));
   }

   float SetValue (unit_of_measure unit, float value)
   {
      _distanceInInches = (value / getConversionFactor(unit));
   }

private:   

   float getConversionFactor(unit_of_measure unit)
   {
      switch(unit)
      {
         // add each conversion factor here
      }
   }

   float _distanceInInches;
}
ph0enix
I'd combine this answer with the approach of using multiple methods (one per conversion factor). But the idea of having one internal representation is the way to go. +1
rmeador
I would also do it this way - but I would also get rid of the switch statement and just use a look-up table
gatorfax
+3  A: 

If you don't mind a dependency, use Boost.Units

If you want to keep exactly your current API, but simplify its implementation, why not represent your unit in terms of some arbitrary standard (e.g. 1 meter). At the very least, instead of having N^2 (source->dest) possibilities, have 2*N (source->std) (std->dest) conversions.

struct distance_unit {
   char const* name;
   double meters_per_unit;
   distance_unit() : name("meters"),meters_per_unit(1.) {}
   double to_meters(double in_units) { return in_units/meters_per_unit; }
   double to_units(double in_meters) { return in_meters*meters_per_unit; }
};

struct distance {
   double d;
   distance_unit unit;
   distance(double d,distance_unit const& unit) : d(d),unit(unit) {}
   distance(double meters,distance_unit const& unit,bool _)
      : d(unit.to_units(meters)),unit(unit) {}
   distance convert_to(distance_unit const& to) {
        return distance(unit.to_meters(d),to,false);
   }
   friend inline std::ostream& operator<<(std::ostream &o) {
      return o << d << ' ' << unit.name;
   }
};

Of course, the only benefit to this is that exactly representable distances (in terms of their unit) won't become inexact. If you don't care about rounding and exact equality of sums, this is more sensible:

struct distance {
   double meters;
   distance_unit preferred_unit;
   distance(double d,distance_unit const& unit) 
     : meters(unit.to_meters(d)),preferred_unit(unit) {}
   distance(double meters,distance_unit const& unit,bool _)
     : meters(meters),preferred_unit(unit)
   distance convert_to(distance_unit const& to) { 
       return distance(meters,to,false);
   }
   friend inline std::ostream& operator<<(std::ostream &o) {
      return o << unit.to_units(meters) << ' ' << unit.name;
   }

};
wrang-wrang
I'm just learning. I don't actually need this for a real project. Looks cool though. Boost contains anything you could think of!
Bob Dylan
+2  A: 

If you use STL, Create a Map of Map of Conversion constant. So you can get the conversion constant from "from" and "to".

Something like this:

std::map <unit_of_measure, std::map<unit_of_measure, double>> ConversionConstants_FromTo;

ConversionConstants_FromTo(inches)(centimeters) = ...;
ConversionConstants_FromTo(inches)(miles)       = ...;

int Distance::ConvertTo(unit_of_measure convert_unit) {
    return distance*ConversionConstants_FromTo(unit, convert_unit)
}
NawaMan
You don't need a 2D map of values for this, you can always use the base SI unit. The value of `FromTo<inches,miles>` is `FromTo<inches,meters>` / FromTo<miles,meters>` - i.e. specialize the meters case, and reuse that in for the other cases.
MSalters
You are right. I just show this as example. :D
NawaMan
A: 

Separate methods are easier to read in source code, but when the target unit comes e.g. from a user selection, you need a function where you can specify it as parameter.

Instead of the switch statement, you can use a table of scaling factors e.g. between the specific unit and SI unit.

Also, have a look at Boost.Units, it doesn't exactly solve your problem, but it is close enough to be interesting.

peterchen
+1  A: 

There's two levels of analysis I would conduct.

First the interface you offer to the caller. The caller creates a Distance object with a specific unit, and then the convert method changes the unit and corresponding distance, error codes indicate the success or otherwise. Presumably you also have getters to aceess the current unit and corrsponding distance.

Now I don't like such a stateful interface. Why not an interface like

 Distance {

      Distance(unit, value) { // constructor

      float getValue(unit) throws UnsupportedUnitException;
 }

So there's no need for the caller to have any idea of the internal units of the distance. No stateful behaviour.

Then the switch statement is clearly repetitive. that must be a candidate for some refactoring.

Every conversion can be expressed as multiplication. You can have a table, keeping all the conversion factors you support. So you have

       float getConversionFactor(fromUnit, toUnit) throws UnsupportedUnitException

which does the lookup of the conversion factor, and then apply it in your getValue() method

       getValue(requestedUnit) {
             return value * getConversionfactor(myUnit, requestedUnit);
       }
djna
A: 

I would combine NawaMan and ph0enix's answer. Instead of having a map of maps, just have 2 maps full of constants. One map contains conversion from meters, the other map contains the inverse. Then the function is something like (in pseudocode):

function convertTo (baseUnitName, destinationUnitName) {
    let A = getInverseConstant(baseUnitName);
    let B = getConstant(destinationUnitName);

    return this.baseUnit*A*B;
}

Which is considerably shorter than your mountainous switch switch statement, and two maps full of constants are considerably easier to maintain than a switch statement, I think. A map of maps would essentially just be a times table, so why not just store the vertical and horizontal cooeficients instead of an n*m block of memory.

You could even write code to read the constants out of a text file, and then generate the inverse constants with a 1/x on each value.

Breton
+1  A: 

While you're still learning, it might be worth abandoning the use of the switch and enum approach in favour of a family of structs, one per handled unit, each containing the input value and a unique tag type to make them different. This has a few advantages:

  1. conversions can be done by overloads of a function with a common name ("Convert" maybe?) which makes life easier if you want to do conversions in templated code.
  2. the type system means you never accidentally convert gallons to light years as you wouldn't write an overload the compiler could match to dimensionally inappropriate conversions.
  3. execution time of a switch or nested if blocks depends on the number of clauses, the overload approach is switched at compile time

The downside would be the overhead of creating that family of tag classes and the need to wrap your arguments up in the class (struct more likely) appropriate. Once wrapped you wouldn't have the usual numeric operators unless you wrote them.

A technique worth learning though imo.

Mike Burrows
I agree that using types, you can prevent all sorts of errors (especially if you don't provide any dangerous implicit conversions). Unfortunately, C++ makes it annoying to create a "strong typedef" (requiring boilerplate to wrap e.g. all the arithmetic ops) although maybe that will be easier in C++0x.
wrang-wrang
C++ and boilerplate seem to go hand in hand. If you can find an efficient way of generating the boilerplate maybe the type-safety becomes worth the effort. While learning though, the sooner you get comfortable with leveraging the type system the better.
Mike Burrows
+1 Overloading is the way to go.
StackedCrooked
+1  A: 

Here are two more things to think about since there are already quite a few very good ideas floating around here.

(1) If you aren't going to represent lengths as value types, then I would use a namespace full of free functions instead of a class. This is more of a style thing that I like to evangelize - if you don't have state or are thinking about static methods, just use a namespace.

namespace Convert {
  double inchesToCentimeters(double inches) { ... }
  double inchesToMeters(double inches) { ... }
} // end Convert namespace

(2) If you are going to use a value type instead (which is what I would recommend), then consider (what I have been calling) "named constructors" instead of a unit enum as well as a single unit representation.

class Convert {
public:
  static Convert fromInches(double inches) {
      return Convert(inches * 0.0254);
  }
  static Convert fromCentimeters(double cm) {
      return Convert(cm / 100.0);
  }
  ...
  double toInches() const { return meters * 39.370079; }
  double toCentimeters() const { return meters * 100.0; }
  ...
protected:
  Convert(double meters_): meters(meters_) {}
private:
  double meters;
};

This will make your user-land code very readable and you can reap the benefits of choosing whatever internal unit makes your life easy too.

D.Shawley