CPSC 427: Object-Oriented Programming

Michael J. Fischer

Lecture 16
October 31, 2016

Storage Model Revisited

Object parts Recall that an object is a block of memory divided into parts, where each part is an instance of the base class or is an instance of a data member. We say that the parts are embedded in the “parent” or outer object.

Embedded objects are actual objects in their own right. A constructor is called when they are born, and their destructor is called when they are about to die. They can be pointed at and used wherever an object of their type is needed.

However, an embedded object is dependent on its parent and has the same lifetime and storage class as its parent.

Object parts (cont.)

Because an embedded object lives and dies with its parent, it can never be explicitly deleted, even if it lives in the heap.

Rather, it is deleted when its parent is deleted. Neverthtless, its destructor will be called just before it dies, just like for any other object!

Note that embedding order and derivation order are opposites. If class Deriv is derived from class Base, then Base is the parent of Deriv in the class hierarchy, but an instance of Deriv has an instance of Base embedded within it.

Functions Revisited

Global vs. member functions A global function is one that takes zero or more explicit arguments.

Example: f(a, b) has two explicit arguments a and b.

A member function is one that takes an implicit argument along with zero or more explicit arguments.

Example: c.g(a, b) has two explicit arguments a and b and implicit argument c.

Example: d->g(a, b) has two explicit arguments a and b and implicit argument *d.

Note that an omitted implicit argument defaults to (*this), which must make sense in the context.

Example: If g is a member function of class MyClass, then within MyClass, the call g(a, b) defaults to (*this).g(a,b) (or equivalently this->g(a,b)).

Defining global functions

There are three ways to define a global function.

1.
Place the declaration at the top level of your code, outside of any class declarations. Most functions in C are of this kind.
2.
Place the declaration inside a class definition, prefixed by the keyword static. This creates a global function whose name is qualified by the class name. It’s visibility is controlled by the visibility keywords public, protected, and private.
3.
Place the declaration at the top level and prefix its name by static. This creates a C-style static function whose name is visible only within the one compile module. Classes and static member functions provide a better way to provide modularity and control name visibility, so this should not be used in C++. It is retained only for compatibility with C.

Defining member functions

Placing a function declaration inside a class definition creates a member function.

Its definition is considered to be “inside” the class, whether or not it appears in the class or as an out-of-line function in a .cpp file.

Example:

class MyClass {  
protected:  
    double g(const int* a, unsigned b) const;  
};

This defines a member function g with explicit parameters of type const int* and unsigned and implicit parameter of type const MyClass&.

Operator syntax

We have seen the operator keyword used to extend the meaning of operators.

Each binary operator corresponds to a function whose name is operator, but the operator syntax a b does not tell us whether to look for a global or a member function. Possibile meanings:

It could mean either, and the compiler sees if either one matches. If both match, it reports an ambiguity.

Operator extension as member function

Here’s a sketch for how one might go about defining a complex number class.

class Complex {  
private:  
   double re;  // real part  
   double im;  // imaginary part  
public:  
   Complex( double re, double im ) : re(re), im(im) {}  
   Complex operator+(const Complex& b) const {  
      return Complex( re+b.re, im+b.im );  
   }  
   Complex operator*(const Complex& b) const {  
      return Complex( re*b.re - im*b.im, re*b.im + im*b.re );  
   }  
};

Operator extension as global function

We have seen one important example of a global operator extension when we define the output operator on a new class.

Given the choice, it is preferable to use a member operator function.

We use a global form of operator<< because the left hand operator is of predefined type ostream, and we can’t add member functions to that class.

Prefix unary operator extensions

C++ has a number of prefix unary operators

*, -, ++, new,

The corresponding operator functions are

operator*(), operator-(), operator++(),

operator new(),

Postfix unary operator extensions

C++ also has two postfix unary operators

++, --.

The corresponding operator functions are

operator++(int), operator--(int).

This is a special case that breaks all the normal rules, but it works since ++ and -- are not binary operators. The dummy int parameter should be ignored.

Ambiguous operator extensions

class Bar {  
public:  
    int operator+(int y) { return y+2; }  
};  
 
int operator+(Bar& b, int y) { return y+3; }  
 
int main() {  
    Bar b;  
    cout << b+5 << endl;  
}

Compiler reports error: ambiguous overload for ’operator+’ in ’b + 5’.