Getting Started Documentation Glish Learn More Programming Contact Us
Version 1.9 Build 1556
News FAQ
Search Home


next up previous
Next: Warnings Up: AIPS++ Programming Standards DRAFT Version Previous: Standards

Subsections


Guidelines

Encapsulation

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.

Comments

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.

Functions

Parameters and Return Values

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).

Operators

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).

const and functions

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/Templates

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.

Object Interface

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.

Casts

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.

Nested Types

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.

Error Handling

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.

IO Streams

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.

Library Names

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.

Format

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 up previous
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