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 users can and can’t use. You can build your internal
mechanisms into the structure without worrying that users 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 users 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 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 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 {O}
// 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; }
///:~
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 of course
wouldn’t allow it.
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
they are concerned with 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 mangling their own private names:
class Y {
public:
void f();
private:
int mX; // "Self-mangled" 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 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 user portion of the interface is
now clearly distinguished, so there’s no possibility of users
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();
};
#endif // STASH_H ///:~
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 user will neither have to look at it
nor be able to depend on the internal representation of the
Stack:
//: C05:Stack.h
// Nested structs via linked list
#ifndef STACK_H
#define STACK_H
class Stack {
struct Link {
void* data;
Link* next;
void initialize(void* Data, Link* Next);
} * head;
public:
void initialize();
void push(void* Data);
void* peek();
void* pop();
void cleanup();
};
#endif // STACK_H ///:~
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 one that knows about the protection level of class members. There is
no information mangled into the member name that carries through to the linker.
All the protection checking is done by the compiler; it’s vanished by
run-time. Notice
that the interface presented to the user 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 user interacts with, or
(more importantly) a single line of client code.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru