Object handles

For the following examples we will assume a simple Location class:

class Location {
private:
  int x;
  int y;
public:
  int getX();
  void setX(newX:int);
  int getY();
  void setY(newY:int);
};

Dynamic object handles

In most cases we want to use dynamic object handles.

Creating an object:

  new Location(); // or
  new Location;

Please note: In this case the parenthensis () are optional.

This would create the object and then immediately loose it.

Assigning that new object to a variable for use:

Location *here = new Location();

Now the variable "here" holds a handle to the object.

To access member functions / variables, we use the "->" operator:

here->setX(10);
here->setY(5);

cout << here->getX() << " " << here->getY();

To pass object handles to other methods, they have to take object handles as parameters.

class Hominoid
{
...
public:
  goToLocation(Location *target);
};

...

Hominoid *bob = new Hominoid();
Location *here = new Location();
...
bob->goToLocation(here);
...

To return object handles, use the pointer reference data type:

class Hominoid
{
...
public:
  Location *whereAreYou();
};
...
Hominoid *bob = new Hominoid();
...
Location *bobsLocation = bob->whereAreYou();
...

Practice:

Define a class "Computer" that contains an attribute "cpu" of type "Cpu". Show the class definition with the attribute cpu and its getter and setter method. Show the implementation for the getCpu() and setCpu() functions.

class Computer
{
private:
  Cpu *cpu;
public:
  void setCpu(Cpu *);
  Cpu *getCpu();
};
...
void Computer::setCpu(Cpu *newCpu) {cpu = newCpu;}
Cpu *Computer::getCpu() { return cpu; }

About the position of the *:

In C++, the * can either be with the classname OR with the attribute / function name:

Location *pos; // is the same as
Location* pos;  // is the same as
Location * pos; // is the same as
Location*pos;
...
Location *whereAreYou(); // is the same as
Location* whereAreYou();

So logically it would make more sense to put the * to the class name, since we're defining a reference to an object and not to the variable.

The only case where this is not equivalent is when we declare multiple variables / attributes in one line.

Location* y, z;

Here, we would assume this to be equivalent to:

Location* y;  // THIS IS NOT THE CASE
Location* z;  // THIS IS NOT THE CASE

Unfortunately it is equivalent to:

Location* y;
Location z;

Therefore you have two choices:

  • Either always put the * with the attribute / variable name

  • Or never declare multiple attributes / variables in one line.

Since I'm lazy, I chose the first solution. You may pick whichever one you like, but be consistent within the same project!

Disposing of objects:

Since we created new dynamic object, we have to dispose of them properly:

Location *loc = new Location();
...
delete loc;

We use "delete" as learned in an earlier class with pointers.

Practice:

Go back to the Cpu / Computer example. Create one instance of each. Call your setter method. Dispose of both objects properly.

Warning: In OO it is sometimes where complicated to find out WHERE or WHEN to delete objects.

Two problems:

  • Forget to delete

  • Delete multiple times

Forget to delete: Sometimes we can not delete an object immediately, but someone else has to do it. In this case, we may forget to delete.

Example:

class Hominoid
{
...
public:
  Location* dreamPlace();
...
}
...
Location *Hominoid::dreamPlace()
{
  Location *l = new Location();
  l->setX(1);
  l->setY(1);
  return l;
}

...
Hominoid *bob;
...

Location loc = bob->dreamPlace();
// Do something with loc

// now loc should be deleted.
delete loc; loc = null;

But why can't we always delete?

Because then we may run into the next problem, deleting to many times / in the wrong place. Example:

class Hominoid
{
private:
  Location *l;
...
public:
  Location *getLocation();
...
}
...
Location *Hominoid::getLocation() {
  return l;
}
...
Hominoid *bob;
...
Location *loc = bob->getLocation();
// do something with loc;

// loc should not be deleted, bob still needs it!

Unlike most other languages (ObjC, Java, ... ) C++ does not have a good solution for this problem. The only solution is very very very very careful programming and good documentation.

Static object handles

So the whole deleting gives us headaches. Can't we just use static data members instead? Aren't they just much easier?

They are easier, alright, because they get automatically allocated and deleted!

Example:

{
  Location l; // l gets automatically allocated here
  l.setX(5);
  l.setY(7);
  ...
}  // l gets automatically deleted here when it runs out of scope

Unfortunately, whenever we use l it gets copied. Here's a larger example:

...
void advance(Location l) // This would usually be somewhere in some class
{
  l.setX(l.getX()+1);
}
...
Location z;
...
z.setX(1);
z.setY(1);
advance(z);
cout << z.getX(); // Prints 1

We can get around the problem here by using pass-by-reference. However, this only works in some cases. Imagine a setter method:

void Hominoid::setLocation(Location &l)
{
  Mylocation = l; // would still copy
}

Also please note: Static object references can not be used with incomplete class definitions! You have to have either a full class definition or a dynamic object reference (pointer).

Conclusion:

  • Static references are easier

  • Unfortunately they may copy objects

  • Use with caution!

  • You need to practice with new and delete anyways, so you may just use dynamic handles everywhere.

A good rule:

Use static object references when you

  • Use that object in only one function

  • Never pass that object as a parameter.

Example: Main function

int main() {
  Game g;
  g.play();
  return 0;
}

We have already used some classes with static references:

  • string is a class.

  • ifstream is a class.

  • ofstream is a class.