The
class
Access
control is often referred to as
implementation
hiding.
Including functions within structures (encapsulation)
produces a data type with characteristics and behaviors, but access control
puts boundaries within that data type, for two important reasons. The first is
to establish what the client programmers can and can’t use. You can build
your internal mechanisms into the structure without worrying that client
programmers will think it’s part of the interface they should be using.
This
feeds directly into the second reason, which is to separate the interface from
the implementation.
If the structure is used in a set of programs, but the client programmers
can’t do anything but send messages to the
public
interface, then you can change anything that’s
private
without requiring modifications to their code.
Encapsulation
and implementation hiding, taken together, invent something more than a C
struct.
We’re now in the world of object-oriented programming, where a structure
is describing a class of objects, as you would describe a class of fishes or a
class of birds: Any object belonging to this class will share these
characteristics and behaviors. That’s what the structure declaration has
become, a description of the way all objects of this type will look and act.
In
the original OOP language,
Simula-67,
the keyword
class
was
used to describe a new data type. This apparently inspired Stroustrup to choose
the same keyword for C++, to emphasize that this was the focal point of the
whole language: the creation of new data types that are more than just C
structs
with functions. This certainly seems like adequate justification for a new
keyword.
However,
the use of
class
in C++ comes close to being an unnecessary keyword. It’s identical to the
struct
keyword in absolutely every way except one:
class
defaults to
private,
whereas
struct
defaults to
public.
Here are two structures that produce the same result:
//: C05:Class.cpp
// Similarity of struct and class
struct A {
private:
int i, j, k;
public:
int f();
void g();
};
int A::f() {
return i + j + k;
}
void A::g() {
i = j = k = 0;
}
// Identical results are produced with:
class B {
int i, j, k;
public:
int f();
void g();
};
int B::f() {
return i + j + k;
}
void B::g() {
i = j = k = 0;
}
int main() {
A a;
B b;
a.f(); a.g();
b.f(); b.g();
The
class
is the fundamental OOP concept in C++. It is one of the keywords that will
not
be
set in bold in this book – it becomes annoying with a word repeated as
often as “class.” The shift to classes is so important that I
suspect Stroustrup’s preference would have been to throw
struct
out altogether, but the need for backwards compatibility with C wouldn’t
allow that.
Many
people prefer a style of creating classes that is more
struct-like
than class-like, because you override the “default-to-
private”
behavior of the class by starting out with
public
elements:
class X {
public:
void interface_function();
private:
void private_function();
int internal_representation;
The
logic behind this is that it makes more sense for the reader to see the members
of interest first, then they can ignore anything that says
private.
Indeed, the only reasons all the other members must be declared in the class at
all are so the compiler knows how big the objects are and can allocate them
properly, and so it can guarantee consistency.
The
examples in this book, however, will put the
private
members first, like this:
class X {
void private_function();
int internal_representation;
public:
void interface_function();
Some
people even go to the trouble of decorating their own private names:
class Y {
public:
void f();
private:
int mX; // "Self-decorated" name
Because
mX
is already hidden in the scope of
Y,
the
m
is unnecessary. However, in projects with many global variables (something you
should strive to avoid, but which is sometimes inevitable in existing projects)
it is helpful to be able to distinguish, inside a member function definition,
which data is global and which is a member.
Modifying
Stash to use access control
It
makes sense to take the examples from the previous chapter and modify them to
use classes and access control. Notice how the client programmer portion of the
interface is now clearly distinguished, so there’s no possibility of
client programmers accidentally manipulating a part of the class that they
shouldn’t.
//: C05:Stash.h
// Converted to use access control
#ifndef STASH_H
#define STASH_H
class Stash {
int size; // Size of each space
int quantity; // Number of storage spaces
int next; // Next empty space
// Dynamically allocated array of bytes:
unsigned char* storage;
void inflate(int increase);
public:
void initialize(int size);
void cleanup();
int add(void* element);
void* fetch(int index);
int count();
};
The
inflate( )
function has been made
private
because it is used only by the
add( )
function and is thus part of the underlying implementation, not the interface.
This means that, sometime later, you can change the underlying implementation
to use a different system for memory management.
Other
than the name of the include file, the above header is the only thing
that’s been changed for this example. The implementation file and test
file are the same.
Modifying
Stack to use
access
control
As
a second example, here’s the
Stack
turned into a class. Now the nested data structure is
private,
which is nice because it ensures that the client programmer will neither have
to look at it nor be able to depend on the internal representation of the
Stack:
//: C05:Stack2.h
// Nested structs via linked list
#ifndef STACK2_H
#define STACK2_H
class Stack {
struct Link {
void* data;
Link* next;
void initialize(void* dat, Link* nxt);
}* head;
public:
void initialize();
void push(void* dat);
void* peek();
void* pop();
void cleanup();
};
As
before, the implementation doesn’t change and so is not repeated here.
The test, too, is identical. The only thing that’s been changed is the
robustness of the class interface. The real value of access control is during
development, to prevent you from crossing boundaries. In fact, the compiler is
the only thing that knows about the protection level of class members. There is
no access control information mangled into the member name that carries through
to the linker. All the protection checking is done by the compiler; it has
vanished by runtime. Notice
that the interface presented to the client programmer is now truly that of a
push-down stack.
It happens to be implemented as a linked list,
but you can change that without affecting what the client programmer interacts
with, or (more importantly) a single line of client code.
Contact: webmaster@codeguru.com
CodeGuru - the website for developers.