PHYCS 3730/5730
Lab Exercise

Exercise 1.

This is a review of operator overloads, based on your vec2d class from the previous exercise. The idea here is to summarize operator overload strategies and good programming style.

The vec2d class definition, as in the file c++strings.cc in the class examples directory, may have its operator overloads organized as follows. Here we will work with overloads of +=,-=,*=,/=,+,-,*,/.

  class vec2d {
  ...
    vec2d& operator+=(const vec2d& v) { x+=v.x; ... ; return *this; }
    vec2d& operator-=(const vec2d& v) { x-=v.x; ... ; return *this; }
    vec2d& operator*=(double) { x*=v.x; ... ; return *this; }
    vec2d& operator/=(double) { x/=v.x; ... ; return *this; }
  ...
  };

  vec2d operator+(const vec2d& u, const vec2d& v)
  { vec2d sum=u; return sum+=v; }

  vec2d operator-(const vec2d& u, const vec2d& v)
  { vec2d diff=u; return diff-=v; }

  // vec2d * double: 
  vec2d operator*(const vec2d& u, double a)
  { vec2d prod=u; return prod+=a; }

  // double * vec2d;
  vec2d operator*(double a, const vec2d& u) 
  { vec2d prod=u; return prod+=a; }

  vec2d operator/(const vec2d& u, double a)
  { double ia=1.0/a; vec2d quo=u; return quo*=ia; }

  ...
Key points (recommendations, not necessarily requirements):

Placement of operator overload definitions.

  • When you need to fiddle directly with class members ("x" and "y" in this case), put the operator overload in the class definition. Also if you have a case where you return the left-hand side of an operator (as in "q+=r"), put it inside the definition and return "*this". (By default, this refers to the argument to the left of the operator.) This means "+=", "-=", "*=" and "/=", are good candidates.

  • When you do not need to modify any of an operator's arguments, you might prefer to put them outside/just below your class definition. Examples: "+", "-", "*", and "/".

  • When you are overloading an operator for use on a particular class that has a left and a right argument (e.g., "8.0*v"), AND the left argument is not a class object, then you MUST put the overload outside of your class definition (since by default, all overloads inside the definition implicitly have class objects on the left of an operator). Example: overload of "*" for a double times a vec2d object.

  • If you ever want to define an operator which can take only a right-side argument, as in "v = -u;", (here, the "-" operator works on "u" with no lefthand operator), you could put the overload wherever you want, but probably put it outside of the class definition since this does not return "*this" or require class members to be manipulated. Thus you could try
        inline vec3d operator-(const vec3d& v) { return v*-1.0; }
    
    Note: operators which can handle only one argument are labeled "unary", whereas operators with both left and right arguments are "binary".

  • (Incidental:) There are two special unary operators that can take a single argument on either the left or the right. These are the unit increment/decrement operators "++" and "--" (e.g., "i++" and "++i" are both valid. Just be aware that if you ever have to overload these, the overload of the "++i" form can be done in the same way as "(-i)" (operator has a right-hand arg only). The other form, "i++", is a little hokier to implement, involving a wierd dummy argument "(int)" in the operator overload definition to distinguish it from the "++i" form. (See B. Stroustrup, The C++ Programming Language, pp. 291,292.)

  • (Important:) Above all, choose the placement of your overload definitions so that your code is well-organized and readable. E.g., the "*" operator has three forms in the vec2d case, on of which cannot be put inside the class definition. So, keep all of them together and put them outside of the class definition.

    Argument types:

  • Pass vec2d objects to operator-functions using "const vec2d&" types if you do not indent to modify these objects. This is most efficient and provides for fairly general usage. The "const" tells the compiler that you won't try to change the argument via its reference. If you fail to do this, you may quickly end up with the compiler yelling at you. (See below!)

  • If you don't care that arguments get passed efficiently, you could also use types "vec2d" (no "const", no reference). This is generally safe to do since the compiler will simply copy the value of the argument to be used locally.

  • If you pass a standard type, e.g., "double", "int", don't bother with "const" or references either.

  • If you want the value of an argument to be manipulated by your operator (e.g., the variable "u" in "u+=v"), that arg should be of type "vec2d&" (a reference) and NOT a "const" type. This will tell the compiler that it has every right to complain if you try to pass a vec3d object which can't be modified. Note: If you do the overload in the class definition. you will not have to worry about this point (just use the "*this" syntax). In usual cases like "+=", the right-hand argument must still be declared as a "const vec2d&" type, since it will not be modified.

    Return types:

  • It is almost always Ok to declare an operator to return a standard type or your own class object, e.g.,
    double operator*(...) {...}
    vec3d operator+(...) {...}
    
    This causes the compiler to reate a copy of the return value for use where the operator is invoked. Be aware that this might be inefficient in some cases, though it should be otherwise safe. This return type is good for the "+", "-", "*", and "/" operators.

  • When dealing with an argument whose value changes (that is, it is passed as a (modifiable) reference) and is itself returned, return a reference to it. This just tells the compiler it doesn't need to make a new copy, since the return result is already stored in the argument. An example is the "+=" operator. (In general if you "return *this", you can set the return value to be a reference to a class object.)

    General Issues:

  • Choose your operators so that they closely match their intended function. For example, the "*" operator is used in the vec2d example for multiplicatoin with a double (two forms) and for the dot product. Maybe you don't like having "*" mean both of these very distinct operations. You might choose "^" or "&" for the dot product instead (overloading the more obvious "." is not allowed in C++ -- why not?). But be careful....

  • ....operator "precedence" can bite you if you do not watch out. Suppose you choose "&" for the dot product. Then try
       cout << v&u << endl;
    
    This will bomb because the compiler will try to do the <<'s first, then the & second since the precedence of the latter is not as high as the former. (This does make a mess: what is the meaning of a double operated upon with an "endl" character by the << (shift-left) operator?


    Exercise 2.

    The idea in the previous exercise is help if you want recommendations for writing and organizing operator overloads. However, if you've gotten this far and actually want to do an exercise, try removing the "const" from the two arguments in the "+" operator for vec2d objects. Then try

    vec2d t,u,v,w; 
    ... 
    t = u+(v+w);
    
    Does this work? Why or why not? Think like a compiler here. You have to first do the sum "v+w", store the result (which should NOT be modifiable!) for use in the next step, addition with "u".

    Next, check out the potential hazards of operator precendence by overloading the & operator to mean vector dot product. Then try the situation given above,

       cout << v&u << endl;
    
    Cure the problem by putting parentheses around v&u.

    Exercise 3.

    This is an example of function overloading. Suppose you wanted to define the cmath function exp() to work like this:

       vec2d v(1,2);
       vec2d ev = exp(v);
    
    where the members of ep are ep.x=exp(v.x) and ep.y=exp(v.y), where here exp is the usual exponential function taking one double argument.

    Set this up using a "function overload": Put

       vec2d exp(const vec2d& v)
       { vec2d e(exp(v.x),exp(v.y)); return e; }
    
    outside your class definition (do not make this a member function!). Next try overloading the abs() function, which for double type arguments returns tha absolute value. Set it up so that "abs(v)" returns the modulus of vec2d v.