MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

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.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru