CS 202 Fall 2013  >  Notes for Thursday, October 17, 2013

CS 202 Fall 2013
Notes for Thursday, October 17, 2013

Object-Oriented Programming: Class Hierarchies [15.7]

Abstract Base Classes

When we deal with a class hierarchy, we usually never create base-class objects. Rather, we use the base class only as a way to specify an interface, which derived classes implement. We can make the creation of base-class objects impossible, by turning the base class into an abstract class.

An abstract class is a class with one or more members that are pure virtual functions. We create a pure virtual function like this.

[C++]

class Foo {
    ...

    virtual void bar() = 0;  // "= 0;" makes this pure virtual

    ...

The above does not mean we define member function bar outside the class. Rather, we do not define it at all.

When we create a derived class of Foo, then we need to override function bar. (If we do not define bar in a derived class, then the derived class is abstract, too.)

[C++]

class BlahBlah : public Foo {
    ...

    virtual void bar()
    {
        ...  // Now class BlahBlah is not abstract
    }

    ...

Polymorphic Objects in Containers [NOT IN TEXT]

The Problem

Suppose we have a base class Base and several derived classes: Derived1, Derived2, etc. We want to make a container (for example, a vector) of polymorphic objects.

How do we do this? A vector<Base> will not allow for derived-class objects at all. For polymorphism to work, base-class objects must be referred to by pointer or reference. We cannot put references in a vector (so vector<Base &> is not an option).

We could make a container of pointers: vector<Base *>. (Note: This would work, but I do not like it much. Read on for why.)

Now, how do we put new objects in the container? There are a couple of pitfalls waiting for us here. Consider the following code.

[C++]

void addToContainer(vector<Base *> & v)
{
    Derived1 x;
    v.push_back(&x);  // There is a problem here!
}

Do you see the problem? The object x is a local variable. It goes away when the function ends. The container will end up holding a pointer to nothing.

We can avoid the problem above if we use dynamic allocation.

[C++]

void addToContainer(vector<Base *> & v)
{
    v.push_back(new Derived1);
}

The above code is good. However, it does give us the reponsibility of doing a delete on all those pointers before the container is destroyed.

[C++]

// Need to do this eventually
for (int i = 0; i < v.size(); ++i)
{
    delete v[i];
}

A Solution: shared_ptr

To solve the above problem, the C++ Standard Library provides shared_ptr. This is an object that acts like a pointer, but does a delete when it is destroyed. The class is defined in header <memort>, along with a useful helper function: make_shared.

[C++]

#include <memory>
using std::shared_ptr;
using std::make_shared;

Like vector, shared_ptr is a template; you must tell it what it points to.

[C++]

shared_ptr<Base> bp;

The above will act like a (Base *), but it will also do the delete for us, when we are done with it.

The solution to our container problem is to make a container of shared_ptr<Base>. When declaring this, we need to put a space between the two ending “>”; otherwise, they will look like a stream operator.

[C++]

vector<shared_ptr<Base> > v;

Adding Items to a Container of shared_ptr

A convenient way to add new items to our container is to use function make_shared. This function does a constructor call for us. We give it the type to construct (between angle brackets). As parameters, we pass the constructor parameters. The return value is a shared_ptr.

[C++]

// To add an object like this to the container:
//   Derived1 x;
v.push_back(make_shared<Derived1>());

// To add an object like this to the container:
//   Derived2 x(34, "abc");
v.push_back(make_shared<Derived2>(34, "abc"));

Using Items in a Container of shared_ptr

Remember that a shared_ptr acts like a pointer. If we want to call a member function on an object in our container, we use the arrow operator (“->”). For example, here is code to call member function action on every object in our container.

[C++]

for (int i = 0; i < v.size(); ++i)
    v[i]->action();

Putting all the above together, we get a container of polymorphic objects (virtual functions are called correctly) without having to worry about doing delete on all the pointers.

For today’s lab work, see the 10/17 Challenge.


CS 202 Fall 2013: Notes for Thursday, October 17, 2013 / Updated: 17 Oct 2013 / Glenn G. Chappell