Whitepaper:
SQL Server Application Platform Optimization
|
Whitepaper:
Simplified SQL Server Deployment and Management
|
Whitepaper:
Consolidating Microsoft SQL Server
|
Whitepaper:
Easing the Migration to Microsoft SQL Server
|
Abstract
base classes and pure virtual functions
Often
in a design, you want the base class to present
only
an interface for its derived classes. That is, you don’t want anyone to
actually create an object of the base class, only to upcast to it so that its
interface can be used. This is accomplished by making that class
abstract
by
giving it at least one
pure
virtual function
.
You can recognize a pure virtual function because it uses the
virtual
keyword and is followed by
=
0
.
If anyone tries to make an object of an abstract class, the compiler prevents
them. This is a tool that allows you to enforce a particular design.
When
an abstract class is inherited, all pure virtual functions must be implemented,
or the inherited class becomes abstract as well. Creating a pure virtual
function allows you to put a member function in an interface without being
forced to provide a possibly meaningless body of code for that member function,
and at the same time forcing inherited classes to provide a definition for it.
In
all the instrument examples, the functions in the base class
Instrument
were always “dummy” functions. If these functions are ever called,
they indicate you’ve done something wrong. That’s because the
intent of
Instrument
is to create a common interface for all the classes derived from it.
[[
corrected diagram here ]]
The
only reason to establish the common interface
is so it can be expressed differently for each different subtype. It
establishes a basic form, so you can say what’s in common with all the
derived classes. Nothing else. Another way of saying this is to call
Instrument
an
abstract
base class
(or
simply an
abstract
class
).
You create an abstract class when you want to manipulate a set of classes
through this common interface.
Notice
you are only required to declare a function as
virtual
in the base class. All derived-class functions that match the signature of the
base-class declaration will be called using the virtual mechanism. You
can
use the
virtual
keyword in the derived-class declarations (and
some people do, for clarity), but it is redundant.
If
you have a genuine abstract class (like
Instrument),
objects of that class almost always have no meaning. That is,
Instrument
is meant to express only the interface, and not a particular implementation, so
creating an
Instrument
object makes no sense, and you’ll probably want to prevent the user from
doing it. This can be accomplished by making all the virtual functions in
Instrument
print error messages, but this delays the information until runtime and
requires reliable exhaustive testing on the part of the user. It is much better
to catch the problem at compile time.
C++
provides a mechanism for doing this called the
pure
virtual function
.
Here is the syntax used for a declaration:
By
doing this, you tell the compiler to reserve a slot for a function in the VTABLE,
but not to put an address in that particular slot. If only one function in a
class is declared as pure virtual, the VTABLE is incomplete. A class containing
pure virtual functions is called a
pure
abstract base class. If
the VTABLE for a class is incomplete, what is the compiler supposed to do when
someone tries to make an object of that class? It cannot safely create an
object of a pure abstract class, so you get an error message from the compiler
if you try to make an object of a pure abstract class. Thus, the compiler
ensures the purity of the abstract class, and you don’t have to worry
about misusing it.
Here’s
Wind4.cpp
modified to use pure virtual functions:
//: C15:Wind5.cpp
// Pure abstract base classes
#include <iostream>
using namespace std;
enum note { middleC, Csharp, Cflat }; // Etc.
class Instrument {
public:
// Pure virtual functions:
virtual void play(note) const = 0;
virtual char* what() const = 0;
// Assume this will modify the object:
virtual void adjust(int) = 0;
};
// Rest of the file is the same ...
class Wind : public Instrument {
public:
void play(note) const {
cout << "Wind::play" << endl;
}
char* what() const { return "Wind"; }
void adjust(int) {}
};
class Percussion : public Instrument {
public:
void play(note) const {
cout << "Percussion::play" << endl;
}
char* what() const { return "Percussion"; }
void adjust(int) {}
};
class Stringed : public Instrument {
public:
void play(note) const {
cout << "Stringed::play" << endl;
}
char* what() const { return "Stringed"; }
void adjust(int) {}
};
class Brass : public Wind {
public:
void play(note) const {
cout << "Brass::play" << endl;
}
char* what() const { return "Brass"; }
};
class Woodwind : public Wind {
public:
void play(note) const {
cout << "Woodwind::play" << endl;
}
char* what() const { return "Woodwind"; }
};
// Identical function from before:
void tune(Instrument& i) {
// ...
i.play(middleC);
}
// New function:
void f(Instrument& i) { i.adjust(1); }
int main() {
Wind flute;
Percussion drum;
Stringed violin;
Brass flugelhorn;
Woodwind recorder;
tune(flute);
tune(drum);
tune(violin);
tune(flugelhorn);
tune(recorder);
f(flugelhorn);Pure
virtual functions are very helpful because they make explicit the abstractness
of a class and tell both the user and the compiler how it was intended to be
used.
Note
that pure virtual functions prevent a function call with the pure abstract
class being passed in by value. Thus it is also a way to prevent object slicing
from accidentally upcasting by value. This way you can ensure that a pointer or
reference is always used during upcasting.
Because
one pure virtual function prevents the VTABLE from being generated
doesn’t mean you don’t want function bodies for some of the others.
Often you will want to call a base-class version of a function, even if it is
virtual. It’s always a good idea to put common code as close as possible
to the root of your hierarchy. Not only does this save code space, it allows
easy propagation of changes.
Pure
virtual definitions
It’s
possible to provide a definition for a pure virtual function in the base class.
You’re still telling the compiler not to allow objects of that pure
abstract base class, and the pure virtual functions must be defined in derived
classes in order to create objects. However, there may be a piece of code you
want some or all the derived class definitions to use in common, and you
don’t want to duplicate that code in every function. Here’s what it
looks like:
//: C15:Pvdef.cpp
// Pure virtual base definition
#include <iostream>
using namespace std;
class Base {
public:
virtual void v() const = 0;
virtual void f() const = 0;
// Inline pure virtual definitions illegal:
//! virtual void g() const = 0 {}
};
// OK, not defined inline
void Base::f() const {
cout << "Base::f()\n";
}
void Base::v() const { cout << "Base::v()\n";}
class D : public Base {
public:
// Use the common Base code:
void v() const { Base::v(); }
void f() const { Base::f(); }
};
int main() {
D d;
d.v();
d.f();The
slot in the
Base
VTABLE
is still empty, but there happens to be a function by that name you can call in
the derived class.
The
other benefit to this feature is that it allows you to change to a pure virtual
without disturbing the existing code. (This is a way for you to locate classes
that don’t redefine that virtual function).