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.