Home arrow C++ Programming arrow Hiding the Implementation

Language Translator

Hacking Zone

Hacking Tools
Attacking

Configure Windows

Windows Configuration

Novels

Mix Novels

Human Personality

Body Language
Hiding the Implementation PDF Print E-mail
Written by Hemanshu Patel   
Monday, 22 October 2007
Article Index
Hiding the Implementation
Page 2
Page 3

Nested friends

Making a structure nested doesn’t automatically give it access to private members. To accomplish this, you must follow a particular form: first, declare (without defining) the nested structure, then declare it as a friend, and finally define the structure. The structure definition must be separate from the friend declaration, otherwise it would be seen by the compiler as a non-member. Here’s an example:

//: C05:NestFriend.cpp
// Nested friends
#include <iostream>
#include <cstring> // memset()
using namespace std;
const int sz = 20;

struct Holder {
private:
int a[sz];
public:
void initialize();
struct Pointer;
friend struct Pointer;
struct Pointer {
private:
Holder* h;
int* p;
public:
void initialize(Holder* h);
// Move around in the array:
void next();
void previous();
void top();
void end();
// Access values:
int read();
void set(int i);
};
};

void Holder::initialize() {
memset(a, 0, sz * sizeof(int));
}

void Holder::Pointer::initialize(Holder* rv) {
h = rv;
p = rv->a;
}

void Holder::Pointer::next() {
if(p < &(h->a[sz - 1])) p++;
}

void Holder::Pointer::previous() {
if(p > &(h->a[0])) p--;
}

void Holder::Pointer::top() {
p = &(h->a[0]);
}

void Holder::Pointer::end() {
p = &(h->a[sz - 1]);
}

int Holder::Pointer::read() {
return *p;
}

void Holder::Pointer::set(int i) {
*p = i;
}

int main() {
Holder h;
Holder::Pointer hp, hp2;
int i;

h.initialize();
hp.initialize(&h);
hp2.initialize(&h);
for(i = 0; i < sz; i++) {
hp.set(i);
hp.next();
}
hp.top();
hp2.end();
for(i = 0; i < sz; i++) {
cout << "hp = " << hp.read()
<< ", hp2 = " << hp2.read() << endl;
hp.next();
hp2.previous();
}
} ///:~

Once Pointer is declared, it is granted access to the private members of Holder by saying:

friend struct Pointer;

The struct Holder contains an array of ints and the Pointer allows you to access them. Because Pointer is strongly associated with Holder, it’s sensible to make it a member structure of Holder. But because Pointer is a separate class from Holder, you can make more than one of them in main( ) and use them to select different parts of the array. Pointer is a structure instead of a raw C pointer, so you can guarantee that it will always safely point inside the Holder.

The Standard C library function memset( ) (in <cstring>) is used for convenience in the program above. It sets all memory starting at a particular address (the first argument) to a particular value (the second argument) for n bytes past the starting address (n is the third argument). Of course, you could have simply used a loop to iterate through all the memory, but memset( ) is available, well-tested (so it’s less likely you’ll introduce an error), and probably more efficient than if you coded it by hand.

 

Is it pure?

The class definition gives you an audit trail, so you can see from looking at the class which functions have permission to modify the private parts of the class. If a function is a friend, it means that it isn’t a member, but you want to give permission to modify private data anyway, and it must be listed in the class definition so everyone can see that it’s one of the privileged functions.

C++ is a hybrid object-oriented language, not a pure one, and friend was added to get around practical problems that crop up. It’s fine to point out that this makes the language less “pure,” because C++ is designed to be pragmatic, not to aspire to an abstract ideal.

 

Object layout

Chapter 4 stated that a struct written for a C compiler and later compiled with C++ would be unchanged. This referred primarily to the object layout of the struct, that is, where the storage for the individual variables is positioned in the memory allocated for the object. If the C++ compiler changed the layout of C structs, then any C code you wrote that inadvisably took advantage of knowledge of the positions of variables in the struct would break.

When you start using access specifiers, however, you’ve moved completely into the C++ realm, and things change a bit. Within a particular “access block” (a group of declarations delimited by access specifiers), the variables are guaranteed to be laid out contiguously, as in C. However, the access blocks may not appear in the object in the order that you declare them. Although the compiler will usually lay the blocks out exactly as you see them, there is no rule about it, because a particular machine architecture and/or operating environment may have explicit support for private and protected that might require those blocks to be placed in special memory locations. The language specification doesn’t want to restrict this kind of advantage.

Access specifiers are part of the structure and don’t affect the objects created from the structure. All of the access specification information disappears before the program is run; generally this happens during compilation. In a running program, objects become “regions of storage” and nothing more. If you really want to, you can break all the rules and access the memory directly, as you can in C. C++ is not designed to prevent you from doing unwise things. It just provides you with a much easier, highly desirable alternative.

In general, it’s not a good idea to depend on anything that’s implementation-specific when you’re writing a program. When you must have implementation-specific dependencies, encapsulate them inside a structure so that any porting changes are focused in one place.

 

The class

Access control is often referred to as implementation hiding. Including functions within structures (often referred to as encapsulation[36]) 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 that these mechanisms are 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 access control, 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 (for “member”) 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 Chapter 4 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();
};
#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 header above 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();
};
#endif // STACK2_H ///:~

As before, the implementation doesn’t change and so it 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 to prevent you from crossing boundaries during development. 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.

 
< Prev   Next >
Your Ad Here

Donate us!!

Enter Amount:

RSS socialnet

Add to MyYahoo!
Subscribe in NewsGator Online
Add to Newsburst
Add to Google
Add to My AOL
Add to Pluck
Subscribe in FeedLounge
Add to Windows Live
Add to NetVibes
Subscribe in Rojo
Subscribe in Bloglines
Add to MyMSN
Add to Plusmo for your cellphone
Add to PageFlakes
Add to Technorati
Add to BlinkBits