If you've used C++ for very long, you're probably familiar with the public, protected, and private keywords. You use these keywords to set the access for data members in a struct or a class. If you're not familiar with these access specifiers, see "Taking Advantage of Private and Protected Class Members" in the July/August 1993 issue of Inside Turbo C++. Experienced C++ programmers usually protect a class's data members from inappropriate access by other classes by making the members protected, or even better, private.
However, if you're writing a member function that needs access to the private data of another instance of the same class, you might try copying that private data by calling access functions that pass back copies of the data. This situation usually occurs when you're creating copy constructors or operator= functions.
Using access functions to copy private data from either large classes or classes with large data members can be very inefficient. When you do so, the compiler will usually copy the data to and from the stack each time you call the access function. Fortunately, any class's member functions have full access to all members of another instance of the same class. As Figure A shows, this means that one instance of class EmployeeRecord can directly use public, protected, and even private members of another EmployeeRecord object via a pointer or reference variable for the other instance.
Figure A - Member functions have full access to the data members of other objects from the same class.
In this article, we'll show you how to make your member functions more efficient by taking advantage of private data access between objects of the same class. Let's begin by looking at the rules for access to private members.
When you define a class that has a private member, you restrict other classes' access to that member. (For the remainder of the article, we'll talk about accessing the data members of classes even though this technique applies to structs equally well.) Figure B shows the definition of a class that has a private array of 20 bytes.
Figure B - Class A has a private member that's inaccessible to other classes.
class A { private: unsigned char byteArray[20]; public: unsigned char getByte(int byteNumber); void setByte(int byteNumber, unsigned char newByte); };
Only member functions of class A (such as getByte() and setByte()) or friend functions can use byteArray. Other classes (even those you derive from class A) or functions can't use byteArray in an object of class A.
To use data despite the private access restriction, most programmers provide access functions such as getByte() and setByte() for classes with private members, like class A. However, most functions you write that provide access to private data will pass that data by value.
For data members that are simple types, pass-by-value access functions are frequently appropriate. For data members that are structs or classes, however, pass-by-value functions may be costly in terms of stack space. For other types of data, such as portions of an array, pass-by-value functions may be very inefficient or impossible to write.
For example, copying all 20 bytes of byteArray to another
class A object by using
the getByte() and setByte() access functions
would be much slower than simply calling the memcpy()
library function with the source and destination pointers. Since
the array is private, this may seem impossible. However,
Borland C++ imposes no restrictions between two objects of the
same class.
Many times, you'll define a member function that has an instance of its own class as one of the function's arguments (as either a pointer or a reference). When this happens, that function can use any of the public, protected, or private members defined within that class.
Member functions use the implicit this pointer to identify the instance data to use at runtime. If the parameter list of a member function supplies additional pointers to objects of the same type, the function has the same full access to the data at those pointer addresses as it has with the this pointer. In fact, you can't restrict member or friend functions on an object-by-object basis, because the compiler treats all pointers of the same type in the same way. Now, let's look at an example of using the private data of another object from inside a member function.
To start our example, launch the Windows version of the Borland C++ Integrated Development Environment (IDE), or you can use the DOS IDE. When the IDE main window opens, choose New from the File menu, and enter the code from Listing A.
Listing A: Class A_Plus
// New and improved version class A_Plus { private: unsigned char byteArray[20]; public: void rangeCopy(A_Plus * source, int start, int count); unsigned char getByte(int byteNumber) { return byteArray[byteNumber]; } void setByte(int byteNumber, unsigned char newByte) { byteArray[byteNumber] = newByte; } }; #include <mem.h> // Allows us to use memcpy() below // This function fails during compile void copyBytes(A_Plus * dest, A_Plus * source) { memcpy((void*)(dest->byteArray), (void*)(source->byteArray), 20); } // This member function compiles without error void A_Plus::rangeCopy(A_Plus * source, int start, int count) { memcpy((void*)&(byteArray[start]), (void*)&(source->byteArray[start]), count); }
Save your source file by choosing Save from the File menu. When the Save File As dialog box appears, enter A_PLUS.CPP in the File Name entry field, and click the OK button.
Figure C - The Compile Status dialog box reports errors in the source file.
Now, compile A_PLUS.CPP by selecting Compile from the Compile window. When the compiler finishes with the source file, the Compile Status dialog box reports two errors and two warnings, as shown in Figure C.
Click the OK button to dismiss the Compile Status dialog box.
As Figure D shows, when the Message window appears, it identifies
the two errors as access violations and the two warnings as unused
parameters.
Figure D - The Message window identifies the locations and types of errors and warnings.
The two errors occur because the function copyBytes() doesn't have legal access to the private member byteArray. Now, double-click on the first error in the Message window to have the IDE position the cursor at the error's location in the file. As Figure E shows, the cursor appears just behind (dest->byteArray).
Figure E - Double-clicking on an error or warning in the Message window moves the source window to the front and moves the cursor to the location of the error.
You'll notice that the compiler didn't complain about the member function rangeCopy(). The rangeCopy() function compiles correctly because, as a member function, it has access rights to any object of its class.
The two warnings in the Message window occur because the compiler can't use the illegal identifiers dest->byteArray and source->byteArray. Before going further, let's change this source file so the copyBytes() function will compile correctly.
As we mentioned earlier, the only functions that have legal access
to byteArray are member functions of class A_Plus
or normal functions that you declare to be friends of class A_Plus.
To allow the copyBytes() function legal access to the
byteArray member of any object of class A, add
the following line to the public section of the class
A definition:
friend void copyBytes(A_Plus * source, A_Plus * dest);Now, choose Compile from the Compile menu to reprocess the A_PLUS.CPP source file. When the compiler finishes with the new version of the source file, the Compile Status dialog box displays no errors
When you design public or protected member functions that modify the private data of another instance of the class, keep in mind that you can pass a publicly inherited class of the member function's class to that function. When you pass an object of a publicly inherited class to a function that expects a base class object, the compiler will "strip off" the data that the inherited class adds to the base class.
For example, let's derive a new class from class A_Plus, which we defined earlier. Figure F shows a possible class structure that adds two new data members to class A_Plus.
Figure F - Class A_PlusPlus adds two new data members to class A_Plus.
class A_PlusPlus : public A_Plus { private: int length; int start; };
You can now use any class A_PlusPlus object as a parameter to the copyBytes() function because the compiler can automatically convert an A_PlusPlus object into an A_Plus object. In addition, you can now call rangeCopy() from an A_Plus object with either an A_Plus object or an A_PlusPlus object. Finally, you can call the rangeCopy() function from an A_PlusPlus object with an A_Plus object or an A_PlusPlus object.
By declaring a member as private, you can prevent nonfriend
and nonmember functions from using that member. However, private
data is still accessible to friend or member functions. In this
article, we've shown you how to take advantage of the access
that member functions can have to the private data of
other instances of their class.
Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.