jeffc said:
Basic rule of thumb is: if the function can NOT do what it was asked to do,
and can't find any kind of reasonable workaround, then throw an exception.
Otherwise, use regular function returns.
Hmmm!
This is a little bit of fun. Say I have a Point class which
I want to have a default constructor so I can do things like:
Point start,end;
curve.end_points(start,end);
Now the problem with a default constructor for something
like Point is that there is no valid default you can give
it. So code like:
Point a,b;
Vector vec(a-b);
is as bad as:
int a,b
int c = a-b;
we'd like to detect when a Point's value is being used
before it is properly set. One way to do this would be to
add some validity flag to the Point class which is false
when default constructed and set true once the point has
been properly initialized. Another way is to encode some
impossible value into the the z value (1.79769e+308). I'll
take the later approach and add a method to check the
validity too:
#include <iostream>
#include <ctime>
#include <cmath>
#include <limits>
#include <cassert>
using namespace std;
class Point{
double m_x;
double m_y;
double m_z;
public:
Point() {
m_z = numeric_limits<double>::max();
}
Point(double x, double y, double z) {set(x,y,z);}
void set(double x, double y, double z) {m_x = x, m_y =
y; m_z = z;}
const double& x() const { return m_x;}
const double& y() const { return m_y;}
const double& z() const { return m_y;}
bool coincident(const Point& a, double eps) const;
bool is_valid() const {
return m_z != numeric_limits<double>::max();
}
};
Now lets consider the function Point::coincident() which
returns true if two points are concident to some tolerance.
However comparing points that haven't been properly
initialized is something we ought to guard against. So I'll
present two versions one using assertions and one using
exceptions, a main to test it:
#ifdef USE_EXCEPTIONS
bool Point::coincident(const Point& a, double eps) const
{
if (!is_valid()) {
throw int(1);
}
return abs(x()-a.x()) < eps &&
abs(y()-a.y()) < eps &&
abs(z()-a.z()) < eps;
}
void test(const Point& a, const Point& b)
{
try {
a.coincident(b, 1e-5);
}
catch (int) {
cout << "caught exception" << endl;
}
}
#else
bool Point::coincident(const Point& a, double eps) const
{
assert(is_valid());
return abs(x()-a.x()) < eps &&
abs(y()-a.y()) < eps &&
abs(z()-a.z()) < eps;
}
void test(const Point& a, const Point& b)
{
a.coincident(b, 1e-5);
}
#endif
int main()
{
time_t start = time(0);
Point a(10.0,10.0,20.0);
Point b(10.0,10.0,20.0);
for (int i = 0; i < 10000000; ++i) {
test(a,b);
}
cout << time(0) - start << endl;
return 0;
}
ideally each of the Point::x(), Point::y() and Point::z()
functions should also check that their instance is valid too
but if you compile and run the program you'll see why I
haven't done that.
Using GCC-3.3.1 on my steam powered PC I get the following
results:
g++ point.C -O2
../a.exe
lapsed time: 6
g++ point.C -O2 -DUSE_EXCEPTIONS
../a.exe
lapsed time: 35
Draw your own moral!