More about the private Access Specifier

Now that we have disposed of the correspondence between arrays and pointers, it's time to return to our discussion of the private access specifier that we've used to control access to the member variables of the class. First of all, let me refresh your memory as to what this access specifier means: only member functions of the string class can refer to variables or functions marked private. As a rule, no member variables of a class should be public. By contrast, most member functions are public, because such functions provide the interface that is used by programmers who need the facilities of the class being defined. However, non-public member functions are sometimes useful for handling implementation details that aren't of interest or use to the "outside world" beyond the class boundaries, as we'll see later.

Now that I've clarified the role of these access specifiers, let's take a look at the program in Figure 8.15. This program won't compile because it tries to refer to m_Length, a private member variable of string.

Figure 8.15. Attempted privacy violation (codestrtst3a.cpp)
#include <iostream>
#include "string3.h"

int main()
{
   string n("Test");

   n.m_Length = 12;

   n.Display();

   return 0;
}

Figure 8.16 is the result of trying to compile this program.

Figure 8.16. Trying to access a private member variable illegally
STRTST3A.cpp:
Error E2247 STRTST3A.cpp 8: 'string::m_Length' is not accessible in function main()

As discussed previously, the reason that we want to prevent access to a member variable is that public member variables cause problems similar to those caused by global variables. To begin with, we want to guarantee consistent, safe behavior of our strings, which is impossible if a nonmember function outside our control can change one of our variables. In the example program, assigning a new value to the m_Length member variable (if that were allowed by the compiler) would trick our Display member function into trying to display 12 characters, when our string contains only four characters of displayable data. Similar bad results would occur if a nonmember function were to change the value of m_Data; we wouldn't have any idea of what it was pointing to or whether we should call delete in the destructor to allow the memory formerly used for our string data to be reused.

Of course, Susan had some questions about access restrictions and public data.

Susan: What's the difference between public and global? I see how they are similar, but how are they different?

Steve: Both global variables and public member variables are accessible from any function, but there is only one copy of any given global variable for the whole program. On the other hand, there is a separate copy of each public member variable for each object in the class where that member variable is defined.

Susan: Okay. Now let me see if I get the problem with Figure 8.15. When you originally wrote m_Length, it was placed in a private area, so it couldn't be accessed through this program?

Steve: Right.

Susan: I am confused on your use of the term nonmember function. Does that mean a nonmember of a particular class or something that is native?

Steve: A nonmember function is any function that is not a member of the class in question.

Susan: A simple concept but easy to forget for some reason.

Steve: Probably because it's stated negatively.

While this may be a convincing argument against letting nonmember functions change our member variables, what about letting them at least retrieve the values of member variables? In other words, why does the private access specifier prevent outside functions from even reading our member variables?

The Advantages of Encapsulation

Unfortunately, even allowing that limited access would be hazardous to the maintainability of our programs, too. The problem here is akin to the other difficulty with global variables: removing or changing the type of a global variable can cause repercussions everywhere in the program. If we decide to implement our string class by a different mechanism than a char* and a short, or even change the names of the member variables from m_Data and m_Length, any programs that rely on those types or names would have to be changed. If our string class were to become popular, this might amount to dozens or even hundreds of programs that would need to be changed if we were to make the slightest change in our member variables. Therefore, the private access specifier rightly prevents nonmember functions from having any direct access to the values of member variables.

Even so, it is sometimes useful for a program that is using an object to find out something about the object's internal state. For example, a user of a string variable might very well want to know how many characters it is storing at the moment, such as when formatting a report. Each string might require a different amount of padding to make the columns on the report line up, depending on the number of visible characters in the string. However, we don't want the length the user sees to include the null byte, which doesn't take up any space on the page. Susan wanted to know how we could allow the user to find out the string's length.

Susan: So how would you "fix" this so that it would run? If you don't want to change m_Length to something public, then would you have to rewrite another string class for this program?

Steve: No, you would generally fix this by writing a member function that returns the length of the string. The GetLength function to be implemented in Figure 8.18 is an example of that.

Susan: Oh, so m_Length stays private but GetLength is public?

Steve: Exactly.

As I've just mentioned to Susan, it is indeed possible to provide such a service without compromising the safety or maintainability of our class, by writing a function that tells the user how long the string is. Figure 8.17 shows the new interface that includes GetLength.

I strongly recommend that you print out the files that contain this interface and its implementation, as well as the test program, for reference as you are going through this part of the chapter; those files are string4.h, string4.cpp, and strtst4.cpp, respectively.

Figure 8.17. Yet another version of the string class interface (codestring4.h)
class string
{
public:
  string();
  string(const string& Str);
  string& operator = (const string& Str);
  ~string();

  string(char* p);
  void Display();
  short GetLength();

private:
  short m_Length;
  char* m_Data;
};

As you can see, all we have done here is to add the declaration of the new function, GetLength. The implementation in Figure 8.18 is extremely simple: it merely returns the number of chars in the string, deducting 1 for the null byte at the end.

Figure 8.18. The string class implementation of the GetLength function (from codestring4.cpp)
short string::GetLength()
{
  return m_Length-1;
}

This solves the problem of letting the user of a string variable find out how long the string is without allowing functions outside the class to become overly dependent on our implementation. It's also a good example of how we can provide the right information to the user more easily by creating an access function rather than letting the user get at our member variables directly. After all, we know that m_Length includes the null byte at the end of the string's data, which is irrelevant to the user of the string, so we can adjust our return value to indicate the "visible length" rather than the actual one.

With this mechanism in place, we can make whatever changes we like in how we store the length of our string; as long as we don't change the name or return type of GetLength, no function outside the string class would have to be changed. For example, we could eliminate our m_Length member variable and just have the GetLength call strlen to figure out the length. Because we're not giving access to our member variable, the user's source code wouldn't have to be changed just because we changed the way we keep track of the length. Of course, if we were to allow strings longer than 32767 bytes, we would have to change the return type of GetLength to something more capacious than a short, which would require our users to modify their programs slightly. However, we still have a lot more leeway to make changes in the implementation than we would have if we allowed direct access to our member variables.

The example program in Figure 8.19 illustrates how to use this new function.

Figure 8.19. Using the GetLength function in the string class (codestrtst4.cpp)
#include <iostream>
using std::cout;
using std::endl;
#include "string4.h"

int main()
{
   short len;
   string n("Test");

   len = n.GetLength();

   cout << "The string has " << len << " characters." << endl;

   return 0;
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset