| Version 1.9 Build 1556
|
|
Next: Warnings
Up: AIPS++ Programming Standards DRAFT Version
Previous: Standards
Subsections
Object operations should be accomplished via member functions. Users of the
object should have no access to the data which the object contains and the
data contained in the object should be as protected as possible. In addition,
member functions should not return pointers or references to any internal
protected data.
The format of comments is described in another document(work in progress). However,
it is often useful to use the C++ style of comments, //, as
opposed to the C style of comments, /* */. By using
the C++ style, whole sections of code can be commented out using
C style comments while debugging a section of code, without worrying
about nested C comments.(See Meyers p.16) Of course another
technique for commenting out a section of code is to put an
#if 0 around it. However, here again care must be taken not to
begin or end the #if 0 inside a C comment.
Pass and return large objects by reference instead of by value, although
sometime objects must be returned by value. This allows for much more efficient
invocation of functions because when objects are passed by value, the constuctors
for the object are called to create a duplicate copy of the object. This can be an
expensive operation. A reference to an object should be returned whenever
it is desirable for the return value to be used as an lvalue. For example,
it might be nice to have an operator[] for an array class. In this case,
the return type of the operator[] should be a reference, so that operations
like A[2] = 9 can be performed. Likewise the operator= should
return a reference so that a = b = c is possible where a, b, and
c are some objects with the operator= defined.
However, sometimes an object must be returned as opposed to a pointer or
reference to a object. For example, when defining an addition operator for
complex numbers:
class Complex \{
friend Complex operator+(const Complex \&,const Complex \&);
public:
Complex(float x=0, float y=0) : r(x), i(y)\{\};
private:
float r,i;
\}
inline Complex operator+(const Complex \&a, const Complex \&b)\{
return(Complex(a.r + b.r,a.i + b.i));
\}
The important thing to note is that the Complex constructed in
operator+ is destroyed as a temporary in expressions like
d = a + b + c where a, b, c, and d are all instances of the
Complex class. If the storage were dynamically allocated
within the operator+, there would be no way of freeing the
storage generated in the a + b sub-expression above
(See Meyers, pp. 78-84).
If you want conversion to happen on both the left and right hand sides of
an operator make the operator a friend instead of a member. For example,
if we extended the previous example to provide a operator- member
function, we would end up with a class that looks like:
class Complex \{
friend Complex operator+(const Complex \&,const Complex \&);
public:
Complex operator-(const Complex \&b){ return(Complex(r - b.r, i - b.i));};
Complex(float x=0, float y=0) : r(x), i(y)\{\};
private:
float r,i;
\};
inline Complex operator+(const Complex \&a, const Complex \&b)\{
return(Complex(a.r + b.r,a.i + b.i));
\}
Both of these classes would work with expressions like c = a + b or
c = a - b where a, b, and c are instances of class Complex, but
in the case of, c = 100 + a; and c = 100 - a, only the
operator+ would succeed because the constructor with defaults
would be applied to 100 to produce a temporary of type Complex.
In the case of operator- the compiler has no way of knowing to which
type the 100 should be converted (See Meyers, 66-71).
Use const parameters for functions whenever possible, use const pointers and
references as return values to avoid the cost of object creation, and
use const member functions whenever the function does not modify the
object. Using the const specifier allows for further specification
of the type signatures of functions. By declaring the parameters as
const, the function accepts a wider number of arguments, i.e. both
const and non-const arguments. By declaring the function as const, the
potential users of the class are told that they can call the function without
worrying about modifications to the object. By returning const pointers and
references, the integrity of the object is maintained without the cost
of copying a portion of the object.
Parameterized types should be used to express a family of types. The most
obvious use is for parameterized container classes which will contain
heterogeneous data, e.g. a linked list. "Do Not use polymorphism
(derived classes with virtual functions) to implement parameterized
types."5 Thus heterogeneous objects should not be
derived from a common class simply for the purpose of making a container
class or whatever from the common class. The primary reason this is
problematic is that the heterogeneous objects are free to mix in
the container class, and the only way to maintain a homogeneous collection
is by runtime type checks. Of course, sometimes this is the desired behavior.
The object interface should be a minimal interface, particularly the public
interface. Members should be as protected as possible, and only the
members designed for users of the class should be public. In addition,
if possible the members should be private, and only when they are intended
for use by derived classes and not for object users should they be made
protected.
Although some times type casts are necessary, they should, in general, be
avoided. The presence of casts undermines the C++ type system. Each
cast should be clearly documented. Also, the casting away of const variables
and objects should be avoided. In fact, attempted modifications to
a const object can either cause an addressing exception or modify the object
as specified. The choice is implementation dependent6.
Downcasts from a virtual base class are always illegal. Sometimes, casts can
be eliminated by using virtual functions in the base class to perform the
necessary operations. Whenever possible the burden of performing operations
specific to derived classes should be shifted from a case statement on
``object type'' to virtual functions and inheritance.
Advantage can be gained by using nested type to avoid name conflicts in the
global name space. The type names can be made local to the class which
utilizes them. For example:
class A \{
public:
enum data \{ IN, OUT, UP, DOWN \};
data getData()\{return x;\}
void setData(data z)\{ x = z;\}
A() : x(IN)\{\};
private:
data x;
\};
main()\{
A a;
A::data t;
t = a.getData();
a.setData(A::UP);
\}
Here data is a type local to the class A, but since it is
public it can still be used as a type name and the elements of the
enumeration can be accessed by prefacing them with the class specifier,
A::. The elements of the enumeration, IN, OUT, etc. must
be unique, i.e. there could not be another enumeration with any of the
same elements as the data enumeration within A.
Exceptions will provide the primary means of flaging and handling errors.
An exception class, will be built for general use. It will be based upon
the OpenSys Inc., Exc C++ exception enabling library and the
1988 Usenix paper on exceptions 7.
It is often advantageous to use the <iostream.h> package because it
is extensible. That is the designer of a package can design functions to
print out his/her classes.
To identify members of libraries, e.g. math, system, etc., the classes
or stand alone public functions belonging to these libraries should be
prefixed with one ore two letters to denote their membership to the
library. Thus, for example:
class mSin \{
...etc...
\};
would belong to the AIPS++ math library. In general, mixed case will
be used to distinguish words within an identifier, e.g.
MyLongIdentifier.
Many feel that it is important to have a format guidelines. These are
standards which make the code ``more readable'' by having a indicating
how control structures and code blocks should look. There are three
primary standards 8. Of these, the Kernighan and
Ritchie format is the control structure format for AIPS++.
So for example:
main(int argc, char *argv[])\{
char *x;
for (int i=1; i < argc; i++) \{
int j = i+1;
while (j < argc) \{
if (strcmp(argv[j],argv[i]) < 0) \{
x = argv[i];
argv[i] = argv[j];
argv[j] = x;
\} else \{
// argv[i] = argv[i]; // Format of else
\}
j += 1;
\}
\}
for (i = 0; i < argc; i++) \{
cout << argv[i] << "\n";
\}
\}
Next: Warnings
Up: AIPS++ Programming Standards DRAFT Version
Previous: Standards
Please send questions or comments about AIPS++ to aips2-request@nrao.edu.
Copyright © 1995-2000 Associated Universities Inc.,
Washington, D.C.
Return to AIPS++ Home Page
2006-10-15