next up previous contents
Next: Objects: Operators, and Overloading Up: C++ for Ocean Modeling Previous: Abstract Data Types

Objects Introduction

As mentioned in the introduction in addition to bundling bits of data together we bundle the operations which can be done on those data into our objects. Using only abstract data types, for example, we have to have a subroutine time_diff(t1, t2, delta) that returns a time difference given two different times. One source of errors is that we have to put the arguments in the right order. Another is that delta may be a different type than t1 and t2 (delta might be an integer, number of hours, rather than a 'date' type of variable). Worse, if we need to add some capabilities to the time_diff subroutine, we may need to change (add to, say) the argument lists and pass additional data (for instance, a change to dates in seconds from dates in minutes or changing delta to be seconds difference rather than a date difference).

By bundling together the operations with the data, we can avoid those opportunities for error. delta = t1 - t2 becomes a legitimate expression. No argument order to remember. What needs to be done is for one person to define how to subtract two dates. We may want the delta to be an integer (number of seconds), in which case by operator overloading (section NN) we'll get the conversion from whatever the - operation returns to an integer.

An illustration of an object (I'll use something like C++ code here) is:

class date {
    int year, month, day, hour, minute, second;


    near(date, window);
    int seconds(date);

What we've done here is to say that dates are given by the year, month, day, hour, minute, second, same as for an abstract data type. We also declare that it will be possible to add, subtract, and equate two dates (the business with (date) is saying that we're declaring a function which takes a date as its argument). More interesting is that we are also declaring that it is meaningful to ask if one date is greater than or less than another date. Now we can use proper logical tests! Much, much, nicer than either explicitly calling a function or inlining the series of tests that would otherwise be needed.

Before proceeding in to the details of objects, I'll list some things that we work with that can easily be considered/ treated as objects.

Buoy reports
Soundings (both atmospheric and oceanic)
Satellite scans
2d grids
3d grids
Metric grids
GRIB messages
BUFR messages
SDR, TDR, EDR files/messages
CEOS data (Alaska SAR facility Radarsat, some ERS data)

The list also serves to introduce the notion that we can nest types of objects. A metric grid, for instance, is a particular type of 2d grid. As I've done it for the ROFS interpolator, for instance, a metric grid is a 2d grid which also has a latitude-longitude < - > ij mapping. Each type of object also can have further descended objects, for instance, the class (type of object) metric grid, can have subclasses:

Polar stereographic
Lambert Conformal
ETA native

And, to continue a bit, Polar stereographic can have subclasses

North ice analysis
South ice analysis
North ice model
South ice model
North Bedient
Nouth Bedient
North NASA
Nouth NASA

The line of descent is then 2d grid -> metric grid -> polar stereographic grid -> North ice analysis (for example). The 2d grid is a nice general object, with quite a few operations that can be specified and typically used. This class has a few thousand lines supporting it. The metric grid adds some capabilities, namely location translation (but, since we don't know what the mapping is, we can't really invoke this class. This is a virtual class, which will be defined in section 6) for only a few lines. Creating a subclass of the metric class requires that we add a couple dozen lines per grid type (namely, describing the mappings between latitude-longitude and i-j) , but once this is done, we can do anything that we can do to any ancestor class - anything that can be done with a 2d grid can be done with a polar stereographic grid. And, here's the important part, nobody needs to write an extra line of code to do this. We make the compiler figure out those parts. The last bit of descent, declaring a north ice analysis grid, takes only the few lines required to specify what is different between the north ice analysis grid and the generic polar stereographic grids. Only a handful of lines, and we make the grid's creator write those. Suppose, now, someone wanted to use a data file that contained a north ice analysis grid. The form for doing so would be:

#include "metric.h" (include the metric grid class)
main program here
north_analysis x; (declare that x is a north_analysis grid)
latpt llocation; (a location in latitude-longitude space)
ijpt ijlocation; (a location in ij space)
int val; (an integer value)
(various set up done here)"b3north.970618"); (read in the north_analysis grid from some file)
x.print("output"); (print it out in plain text)
ylocation = x.locate(ijlocation); (find the latitude-longitude of an ij point)
val = x.get(ylocation); (find the value corresponding to a latitude-longitude location)
val = x.get(ijlocation); (find the value corresponding to an ij location)

Note that our programmer here never needed to know anything at all about the nature of the north_analysis grid, except that there was such a thing and some descriptions of what things can be done to 2d grids. We've gone beyond Fortran libraries because we never need to know those details. Further, we'd write the same program if we were working with a mercator grid, an ETA grid, etc. No arguments change, and no function names change. At some later date, the north_analysis grid could become higher resolution, or change to Lambert Conformal (say), and our programmer would still not need to change a single line of his program.

next up previous contents
Next: Objects: Operators, and Overloading Up: C++ for Ocean Modeling Previous: Abstract Data Types
Robert Grumbine