Chapter 7
Defining Your Own Data Types

  • How structures are used
  • How classes are used
  • The basic components of a class and how you define class types
  • How to create and use objects of a class
  • How to control access to members of a class
  • How to create constructors
  • The default constructor
  • References in the context of classes
  • How to implement the copy constructor

You can find the wrox.com code downloads for this chapter on the Download Code tab at www.wrox.com/go/beginningvisualc. The code is in the Chapter 7 download and individually named according to the names throughout the chapter.

THE STRUCT IN C++

This chapter is about creating your own data types to suit your particular problem. It’s also about creating objects, the building blocks of object-oriented programming. An object can seem a bit mysterious at first, but as you’ll see in this chapter, an object can be just an instance of one of your own data types.

A structure is a user-defined type that you define using the struct keyword so it is often referred to as a struct. The struct originated back in C and C++ incorporates and expands on the C struct. A struct in C++ is functionally replaceable by a class insofar as anything you can do with a struct, you can also achieve by using a class. However, because Windows was written in C before C++ became widely used, the struct appears pervasively in Windows programming. It is also used today, so you really need to know something about structs. You’ll first look at structs before exploring the more extensive capabilities offered by classes.

What Is a struct?

Almost all the variables that you have seen up to now have been able to store a single type of entity — a number of some kind, a character, or an array of elements of the same type. The real world is a bit more complicated than that, and just about any physical object you can think of needs several items of data to describe it even minimally. Think about the information that might be needed to describe something as simple as a book. You might consider title, author, publisher, date of publication, number of pages, price, topic or classification, and ISBN number, just for starters, and you can probably come up with a few more without too much difficulty. You could specify separate variables to contain each of the parameters that you need to describe a book, but ideally, you would have a single data type, Book, say, which embodied all of the things that you need to describe a book. I’m sure you won’t be surprised to hear that this is exactly what a struct can do for you.

Defining a struct

Let’s stick with the notion of a book, and suppose that you just want to include the title, author, publisher, and year of publication within your definition of a book. You could define a structure to accommodate this as follows:

struct Book
{
  char title[80];
  char author[80];
  char publisher[80];
  int year;
};

This doesn’t define any variables, but it does define a new type, and the name of the type is Book. The struct keyword defines Book as a structure, and the elements making up an object of this type are defined within the braces. Note that each line defining an element in the struct is terminated by a semicolon, and that a semicolon also appears after the closing brace. The elements of a struct can be of any type, except the same type as the struct being defined. You couldn’t have an element of type Book included in the structure definition for Book, for example. You may think this is a limitation, but note that you could include a pointer of type Book*, as you’ll see a little later on.

The elements title, author, publisher, and year enclosed between the braces in the preceding definition are referred to as members or fields of the Book structure. A Book object is called an instance of the type. Every object of type Book contains its own set of the members title, author, publisher, and year. You can now create variables of type Book in exactly the same way as you create variables of any other type:

Book novel;                            // Declare variable novel of type Book

This defines a variable with the name novel that you can now use to store information about a book. All you need now is to understand how you get data into the members that make up a variable of type Book.

Initializing a struct

The first way to get data into members of a struct object is to define initial values for them in the definition of the object. Suppose you wanted to initialize novel to contain the data for one of your favorite books, Paneless Programming, published in 1981 by the Gutter Press. This is a story of a guy performing heroic code development while living in an igloo, and, as you probably know, inspired the famous Hollywood box office success, Gone with the Window. It was written by I.C. Fingers, who is also the author of that seminal three-volume work, The Connoisseur’s Guide to the Paper Clip. With this wealth of information, you can write the definition for novel as:

Book novel
{
 "Paneless Programming",              // Initial value for title
 "I.C. Fingers",                      // Initial value for author
 "Gutter Press",                      // Initial value for publisher
  1981                                // Initial value for year
};

The initializing values appear in an initializer list in much the same way as for elements of an array. As with arrays, the sequence of initial values needs to be the same as the sequence of the members of the struct in its definition. Each member of the novel structure has the corresponding initial value assigned to it, as indicated in the comments.

Accessing the Members of a struct

To access individual members of a struct, you use the member selection operator, which is a period; this is sometimes referred to as the member access operator. To refer to a member, you write the struct variable name, followed by a period, followed by the name of the member. To change the year member of the novel structure, you could write:

novel.year = 1988;

This sets the value of the year member to 1988. You can use a member of a struct in exactly the same way as any other variable. To increment the member year by two, for example, you can write:

novel.year += 2;

This increments the value of the year member of novel.

IntelliSense Assistance with Structures

The editor in Visual C++ is quite intelligent — it knows the types of variables, for instance. This is because of the IntelliSense feature. If you hover the mouse cursor over a variable name in the editor window, it pops up a little box showing its type. It also can help a lot with structures (and classes, as you will see) because not only does it know the types of ordinary variables, it also knows the members that belong to a variable of a particular structure type. As you type the member selection operator following a structure variable name, the editor pops up a window showing the list of members. If you click one of the members, it shows the member definition including the comment that appeared in the original definition of the structure, so you know what it is. This is shown in Figure 7-2, using a fragment of the previous example.

image

FIGURE 7-2

There’s a real incentive to add comments, and to keep them short and to the point. If you double-click on a member in the list or press the Enter key when the item is highlighted, it is automatically inserted after the member selection operator, thus eliminating one source of typos in your code. Great, isn’t it?

You can turn any or all of the IntelliSense features off. To turn the IntelliSense features on or off, first select Options from the Tools menu. Expand the Text Editor tree in the left pane of the dialog that displays by clicking the [unfilled] symbol, then click the unfilled symbol alongside C/C++. Click the Advanced option and you will see the IntelliSense options displayed in the right pane. Setting an option to false turns it off.

The editor also shows the parameter list for a function when you are typing the code to call it — it pops up as soon as you enter the left parenthesis for the argument list. This is particularly helpful with library functions, as it’s tough to remember the parameter list for all of them. Of course, the #include directive for the header file must already be there in the source code for this to work. Without it, the editor has no idea what the library function is. Also, if you omit the std namespace prefix when you have also failed to include a using statement for a library function, the editor won’t recognize the function. You will see more things that the editor can help with as you learn more about classes. After that interesting little diversion, let’s get back to structures.

The RECT Structure

Rectangles are used a great deal in Windows programs. For this reason, there is a RECT structure predefined in the windef.h header file that is included in windows.h. Its definition is essentially the same as the structure that you defined in the last example:

struct RECT
{
  LONG left;                            // Top-left point
  LONG top;                             // coordinate pair
        
  LONG right;                           // Bottom-right point
  LONG bottom;                          // coordinate pair
};

Type LONG is a Windows type that is equivalent to the fundamental type long. This struct is usually used to define rectangular areas on your display for a variety of purposes. Because RECT is used so extensively, windows.h also contains prototypes for a number of functions to manipulate and modify rectangles. For example, it defines the InflateRect() function that increases the size of a rectangle, and the EqualRect() function that compares two rectangles.

The MFC defines a CRect class that is the equivalent of a RECT structure. After you understand classes, you will be using this rather than the RECT structure. The CRect class provides an extensive range of functions for manipulating rectangles, and you’ll use many of these when you are writing Windows programs using the MFC.

Using Pointers with a struct

You can create a pointer to an object of a structure type. Many of the functions defined in windows.h that work with RECT objects require pointers to a RECT object as arguments, because this avoids the copying of the structure when a function is called. To define a pointer to a RECT object, the definition is what you might expect:

RECT* pRect {};                  // Define a pointer to a RECT

Assuming that you have defined a RECT object, aRect, you can set the pointer to the address of this variable in the normal way:

pRect = &aRect;                        // Set pointer to the address of aRect

As you saw when I introduced the idea of a struct, a struct can’t contain a member of the same type as the struct being defined, but it can contain a pointer to a struct, including a pointer to a struct of the same type. For example, you could define a structure like this:

struct ListElement
{
  RECT aRect;                          // RECT member of structure
  ListElement* pNext;                  // Pointer to a list element
};

The first member of the ListElement structure is of type RECT, and the second member is a pointer to a structure of type ListElement — the same type as that being defined. This allows objects of type ListElement to be daisy-chained together, where each ListElement can contain the address of the next ListElement object in a chain, the last in the chain having the pointer as nullptr. This is illustrated in Figure 7-3.

image

FIGURE 7-3

Each box in the diagram represents an object of type ListElement, and the pNext member of each object stores the address of the next object in the chain, except for the last object, where pNext is nullptr. This kind of arrangement is referred to as a linked list. It has the advantage that as long as you know the address of the first element in the list, you can find all the others. This is particularly useful when many objects are created dynamically, because you can use a linked list to keep track of them. Every time you create a new object, you can add it to the end of the list by storing its address in the pNext member of the last object in the chain.

Accessing Structure Members through a Pointer

Consider the following statements:

RECT aRect {0, 0, 100, 100};
RECT* pRect {&aRect};

The first defines the aRect object to be of type RECT with the first pair of members initialized to (0, 0) and the second pair to (100, 100). The second statement defines pRect as a pointer to type RECT and initializes it with the address of aRect. You can now access the members of aRect through the pointer with a statement such as this:

(*pRect).top += 10;                  // Increment the Top member by 10

The parentheses around the dereferenced pointer here are essential because the member access operator takes precedence over the dereferencing operator. Without the parentheses, you would be attempting to treat the pointer as a struct and trying to access the member, so the statement would not compile. After executing this statement, the top member will have the value 10 and, of course, the remaining members will be unchanged.

The syntax that you used to access the member of a struct through a pointer looks rather clumsy. Because this kind of operation crops up very frequently, C++ includes a special operator that expresses the same thing in a much more readable and intuitive form, so let’s look at that next.

The Indirect Member Selection Operator

The indirect member selection operator, ->, is specifically for accessing members of a struct or a class through a pointer; this operator is also referred to as the indirect member access operator. The operator looks like a little arrow (->) and is formed from a minus sign (-) followed by the symbol for greater than (>). You could use it to rewrite the statement to access the top member of aRect through the pointer pRect, as follows:

pRect->top += 10;                      // Increment the top member by 10

This is much more expressive of what is going on, isn’t it? You’ll see a lot more of the indirect member selection operator throughout the rest of the book.

TYPES, OBJECTS, CLASSES, AND INSTANCES

Before I get into the language, syntax, and programming techniques for classes, I’ll first explain how your existing knowledge relates to the concept of classes. So far, you’ve learned that you can create variables that can be any of a range of fundamental data types: int, long, double, and so on. You have also seen how you can use the struct keyword to define a structure type that you can use as the type for a variable representing a composite of several other variables.

The variables of the fundamental types don’t allow you to model real-world objects (or even imaginary objects) adequately. It’s hard to model a box in terms of an int, for example; however, you can use the members of a struct to define a set of attributes for such an object. You could define variables length, width, and height to represent the dimensions of the box and bind them together as members of a Box structure, as follows:

struct Box
{
  double length;
  double width;
  double height;
};

With this definition of a new data type called Box, you can define variables of this type just as you did with variables of the basic types. You can then create, manipulate, and destroy as many Box objects as you need to in your program. This means that you can model objects using structs and write your programs around them.

So — that’s object-oriented programming all wrapped up, then? Well, not quite. Object-oriented programming (OOP) is based on three basic concepts relating to object types (encapsulation, polymorphism, and inheritance), and what you have seen so far doesn’t quite fit the bill. Don’t worry about what these terms mean for the moment — you’ll explore that in the rest of this chapter and throughout the book.

The notion of a struct in C++ goes far beyond the original concept of a struct in C — it incorporates the object-oriented notion of a class. This idea of classes, from which you can create your own data types and use them just like the native types, is fundamental to C++, and the class keyword was introduced into the language to describe this concept. The keywords struct and class are almost identical in effect. They differ in the access control to the members, which you’ll learn more about later in this chapter. The struct keyword is maintained in C++ for backwards compatibility with C, but everything that you can do with a struct, and more, you can achieve with a class.

Take a look at how you might define a class representing boxes:

class CBox
{
public:
  double m_Length;
  double m_Width;
  double m_Height;
};

When you define CBox as a class, you are essentially defining a new data type, similar to when you defined the Box structure. The only differences here are the use of the keyword class instead of struct, and the use of the public keyword followed by a colon that precedes the definition of the members of the class. The variables that you define as part of the class are called data members of the class, because they are variables that store data.

The public keyword is a clue as to the difference between a structure and a class. It just specifies that the members of the class that follow the keyword are generally accessible, in the same way as the members of a structure are. By default, the members of a class are not generally accessible and are said to be private, and to make members accessible, you must precede their definitions with the public keyword. The members of a struct, on the other hand, are public by default. The reason that class members are private by default is because, in general, an object of a class should be a self-contained entity such that the data that make the object what it is should be encapsulated and only changed under controlled circumstances. Public data members should be very much the exception. As you’ll see a little later in the chapter, though, it’s also possible to place other restrictions on the accessibility of members of a class.

I have also called the class CBox instead of Box. I could have called the class Box, but the MFC adopts the convention of using the prefix C for all class names, so you might as well get used to it. The MFC also prefixes data members of classes with m_ to distinguish them from other variables, so I’ll use this convention, too. Remember, though, that in other contexts where you might use C++ without the MFC, this will not be the case; in some instances, the convention for naming classes and their members may be different, and in others, there may be no particular convention for naming entities.

You can define a variable, bigBox, which represents an instance of the CBox class type like this:

CBox bigBox;

This is exactly the same as declaring a variable for a struct, or, indeed, for any other variable type. After you have defined the CBox class, definitions for variables of this type are quite standard.

First Class

The notion of class was invented by an Englishman to keep the general population happy. It derives from the theory that people who know their place and function in society are much more secure and comfortable in life than those who do not. The famous Dane, Bjarne Stroustrup, who invented C++, undoubtedly acquired a deep knowledge of class concepts while at Cambridge University in England and appropriated the idea very successfully for use in his new language.

Class in C++ is similar to the English concept, in that each class usually has a very precise role and a permitted set of actions. However, it differs from the English idea because class in C++ has largely socialist overtones, concentrating on the importance of working classes. Indeed, in some ways it is the reverse of the English ideal because working classes in C++ often live on the backs of classes that do nothing at all.

Operations on Classes

You can define new data types as classes to represent whatever kinds of objects you like. Classes (and structures) aren’t limited to just holding data; you can also define member functions or even operations that act on objects of your classes using the standard C++ operators. You can define the CBox class, for example, so that the following statements work and have the meanings you want them to have:

CBox box1;
CBox box2;
        
if(box1 > box2)          // Fill the larger box
  box1.fill();
else
  box2.fill();

You could also implement operations as part of the CBox class for adding, subtracting or even multiplying boxes — in fact, almost any operation to which you could ascribe a sensible meaning in the context of boxes.

I’m talking about incredibly powerful medicine here, and it constitutes a major change in the approach that you take to programming. Instead of breaking down a problem in terms of what are essentially computer-related data types (integers, floating-point numbers, and so on) and then writing a program, you can program in terms of problem-related data types, in other words, classes. These classes might be named CEmployee, or CCowboy, or CCheese, or CChutney, each defined specifically for the kind of problem that you want to solve, complete with the functions and operators that are necessary to manipulate instances of your new types.

Object-oriented program design starts with deciding what application-specific data types you need to solve the problem in hand, defining those as classes and writing the program in terms of operations on the specific types that the problem is concerned with, be they CCoffins or CCowpokes.

Terminology

I’ll first summarize some of the terminology that I will be using when discussing classes:

  • A class is a user-defined data type.
  • Object-oriented programming (OOP) is the programming style based on the idea of defining your own data types as classes, where the data types are specific to the domain of the problem you intend to solve.
  • Declaring an object of a class type is sometimes referred to as instantiation because you are creating an instance of a class.
  • Instances of a class type are referred to as objects.
  • The idea of an object containing the data implicit to its definition, together with the functions that operate on that data, is referred to as encapsulation.

When I get into the details of object-oriented programming, it may seem a little complicated in places, but getting back to the basics usually helps to make things clearer, so always keep in mind what objects are really about. They are about writing programs in terms of the objects that are specific to the domain of your problem. All the facilities around classes are there to make this as comprehensive and flexible as possible. Let’s get down to the business of understanding classes.

UNDERSTANDING CLASSES

A class is a specification of a data type that you define. It can contain data elements that can either be variables of the fundamental types or of other user-defined types. The data elements may be single data items, arrays, pointers, arrays of pointers of almost any kind, or objects of other class types, so you have a lot of flexibility. A class typically contains functions that operate on objects of the class type by accessing the data elements that they include. So, a class combines both the definition of the elementary data that makes up an object and the means of manipulating the data that belongs to objects of the class type.

The data and functions within a class are called members of the class. Oddly enough, the members of a class that are data items are called data members and the members that are functions are called function members or member functions. The member functions are also sometimes referred to as methods; I will not use this term in general in this book, but it will turn up in Chapter 18.

When you define a class, you define a blueprint for objects of that type. This doesn’t actually define any objects, but it does define what the class name means, that is, what an object of the class type will consist of and what operations can be performed on it. It’s much the same as if you wrote a description of the basic type double. This wouldn’t be an actual variable of type double, but a definition of how it’s made up and what you can do with it. To create a variable of a fundamental type, you use a definition statement. It’s exactly the same with a class type.

Defining a Class

Take a look again at the class example you saw earlier — a class of boxes. The CBox type was defined as:

class CBox
{
public:
  double m_Length;                     // Length of a box in inches
  double m_Width;                      // Width of a box in inches
  double m_Height;                     // Height of a box in inches
};

The class type name follows the class keyword, and the three data members are defined between the curly braces. The data members are defined for the class using the definition statements that you already know and love, and the whole class definition is terminated with a semicolon. The names of all the members of a class are local to the class. You can, therefore, use the same names elsewhere in a program, including other class definitions.

Access Control in a Class

The public keyword determines the access attributes of the members of the class that follow it. Specifying members as public means that they can be accessed anywhere within the scope of the class object to which they belong. You can also specify the members of a class as private or protected, in which case the members cannot be accessed from outside the class. I’ll explain these attributes in more detail later. If you omit the access specification, the members have the default access attribute, private. (This is the only difference between a class and a struct — the default access specifier for a struct is public.)

All you have defined so far is the CBox class, which is a type. You haven’t created any objects. When I talk about accessing a class member, say m_Height, I’m talking about accessing the data member of a particular object, and that object needs to be defined somewhere.

Declaring Objects of a Class

You define objects of a class with exactly the same sort of definition that you use to define variables of fundamental types, so you could define objects of the CBox class type with these statements:

CBox box1;                             // Declare box1 of type CBox
CBox box2;                             // Declare box2 of type CBox

Both of these objects will, of course, have their own independent data members. This is illustrated in Figure 7-4.

image

FIGURE 7-4

The object name, box1, embodies the whole object, including its three data members. They are not initialized to anything — the data members of each object will contain junk values, so let’s look at how you access them for the purpose of setting them to some specific values.

Accessing the Data Members of a Class

You can refer to the data members of a class object using the direct member selection operator that you used to access members of a struct. To set the value of the m_Height member of the object box2 to 18.0, you can write this statement:

box2.m_Height = 18.0;                  // Setting the value of a data member

You can access the data member in this way in a function that is outside the class because the m_Height member has public access. If it wasn’t defined as public, this statement would not compile. You’ll see more about this shortly. Obviously, you could assign values to the other public data members of box2.

You can also copy the values of members of one object to another. For example:

CBox box1;
CBox box2;
box1.m_Length = box1.m_Width = box1.m_Height = 2;
box2 = box1;                           // Member-wise copying from box1 to box2

After creating two CBox objects, you set all the members of box1 to 2.0. The last statement copies the values of the box1 members to box2, so all the members of box2 will be 2.0. Member-wise copying from one object to another works regardless of the access specification of the data members.

Memberwise Initialization of an Object

Because the data members of a CBox object are public, you can specify their values in an initializer list when you create the object. For example:

CBox box1 {2.5, 3.5, 4.5};

The members of box1 will be assigned values from the list in sequence so m_Length will be 2.5, m_Width will be 3.5, and m_Height will be 4.5. If you supply fewer initial values in the list than there are data members, the remainder will be set to zero. For example:

CBox box2 {2.5, 3.5};

m_Height for box2 here will be zero.

You can also supply an empty list to initialize all the members to zero:

CBox box3 {};             // All data members 0

You cannot do this if the data members are private. You also cannot use an initializing list in this way if the class includes a constructor definition. I’ll explain what a constructor is a little later in this chapter.

Initializing Class Members

You can specify initial values for data members in the definition of a class type. The members of the CBox class store dimensions of a box, which should not be negative, so it would be sensible to make the initial values 1.0. Here’s how you do this:

class CBox
{
public:
  double m_Length {1.0};               // Length of a box in inches
  double m_Width {1.0};                // Width of a box in inches
  double m_Height {1.0};               // Height of a box in inches
};

The syntax for initializing class members is the same as for ordinary variables. You use an initializer list. The initial values will apply for the members of any CBox object you create unless the member values are set by some other means.

You don’t have to initialize every data member. The ones you don’t provide initial values for will contain junk values. If you provide an initial value for one or more members, you cannot specify initial values when you create an object as I described in the previous section. If you try to do so the compiler will flag it as an error. To restore this capability you must include a constructor in the class. As I said, I’ll explain class constructors a little later in this chapter.

Member Functions of a Class

A member function of a class is a function that has its definition or prototype within the class definition. It operates on any object of the class of which it is a member, and can access all the members of an object, regardless of the access specification. The names of the class members that you use in the body of a member function automatically refer to the members of the specific object used to call the function, and the function can only be called for a particular object of the class type. If you try to call a member function without specifying an object name, your program will not compile. Let’s try it out.

Defining a Member Function Outside a Class

You can define a member function outside the class definition. In this case you just put the prototype inside the class. If you rewrite the CBox class definition with the function definition outside, the class definition looks like this:

class CBox                             // Class definition at global scope
{
public:
  double m_Length {1.0};               // Length of a box in inches
  double m_Width {1.0};                // Width of a box in inches
  double m_Height {1.0};               // Height of a box in inches
  double volume();                     // Member function prototype
};

Because the definition of the Volume() member will be outside the class, there has to be a way of telling the compiler that the function belongs to the CBox class. This is done by prefixing the function name with the name of the class and separating the two with the scope resolution operator. The function definition would look like this:

// Function to calculate the volume of a box
double CBox::volume()
{
  return m_Length*m_Width*m_Height;
}

The function produces the same output as the last example; however, the program isn’t exactly the same. In the second case, all calls to the function are treated in the way that you’re already familiar with. However, when you define a function within the class definition, as in Ex7_03.cpp, the compiler implicitly treats the function as an inline function.

Inline Functions

The compiler tries to expand the code in the body of an inline function in place of a call to the function. This avoids much of the overhead of calling the function and speeds up your code. This is illustrated in Figure 7-5.

image

FIGURE 7-5

Specifying a function as inline does not guarantee the function will be inline. The compiler may not be able to insert the code for a function inline (such as with recursive functions or functions for which you have obtained an address), but generally, it will work. It’s best used for very short, simple functions, such as our volume() function in the CBox class, because such functions will execute faster inline and inserting the body code in place of each call does not significantly increase the size of the executable module. When the compiler is not able to make a function inline, your code will still compile and run.

When you define a member function outside of a class, you can tell the compiler that you would like the function to be considered as inline by placing the keyword inline at the beginning of the function header:

// Function to calculate the volume of a box
inline double CBox::Volume()
{
  return m_Length*m_Width*m_Height;
}

With this function definition, the program is exactly the same as the original. Thus you can put the member function definitions outside of a class and still retain the execution performance benefits of inlining. You can apply the inline keyword to ordinary functions that have nothing to do with classes and get the same effect. Remember, however, that it’s best used for short, simple functions.

Having said all that, it is not essential to use the inline keyword for functions to be inline. The compiler is sometimes smart enough to decide for itself whether inlining makes sense, even if the method is not marked as “inline”.

Next I’ll explain a little more about what happens when you define an object of a class.

CLASS CONSTRUCTORS

In the previous program, you defined the CBox objects, box1 and box2, and then set values for members of each object explicitly. A major constraint on this approach arises when the data members of a class don’t have the attribute public — you won’t be able to access them from outside the class at all. There has to be a better way and of course there is — it’s known as the class constructor.

What Is a Constructor?

A class constructor is a special function in a class that is responsible for creating new objects. A constructor can customize objects as they are created to ensure that data members have the values you want. Because a constructor is a member function, it can set the values for members regardless of their access attributes. A class can have several constructors, enabling you to create objects in various ways. A constructor can and often does include code to check the validity of the arguments passed to it to ensure that data members have legal values for the object type. A trivial example would be to ensure that the dimensions stored in a CBox object was not negative.

You have no leeway in naming constructors — they always have the same name as the class in which they are defined, even when there are two or more. CBox() for example, is a constructor for our CBox class. A constructor has no return type. It’s wrong to specify a return type for a constructor; you must not even write it as void. The primary purpose of a constructor is to assign values to data members of the class, and no return type is necessary or permitted. If you specify a return type for a constructor, the compiler will report it as an error.

The Default Constructor

Try modifying Ex7_04.cpp by adding this definition for box2:

CBox box2;                             // Define box2 of type CBox

When you rebuild this version of the program, you get the error message:

error C2512: 'CBox': no appropriate default constructor available

This means that the compiler is looking for a default constructor to create box2 (also referred to as the noarg constructor because it doesn’t require arguments when it is called). The error message is because you haven’t supplied an initializer list containing the arguments for the constructor defined in the class. The default constructor looks like this:

CBox()                       // Default constructor
{}                           // Totally devoid of statements

A default constructor can be either a constructor that has no parameters as here, or a constructor with all its parameters having default values specified. This statement worked perfectly well in Ex7_02.cpp, so why doesn’t it work now?

The answer is that the previous example used a default constructor that was supplied by the compiler, and the compiler provided this constructor because you didn’t supply any. In this example you did supply a constructor so the compiler assumes that you are taking care of everything needed to create CBox objects and it didn’t supply the default. If you want to use definitions for CBox objects with no initializer list, you have to define the default constructor in the class. You don’t have to write it as the previous fragment in the class. You can instruct the compiler to include the default constructor when it will otherwise be suppressed because you have defined other constructors for the class. Here’s how you would do that for the CBox class:

class CBox                                  // Class definition at global scope
{
public:
  double m_Length{ 1.0 };                   // Length of a box in inches
  double m_Width{ 1.0 };                    // Width of a box in inches
  double m_Height{ 1.0 };                   // Height of a box in inches
 
  // Constructor definition
  CBox(double lv, double wv, double hv)
  {
    cout << "Constructor called." << endl;
    m_Length = lv;                          // Set values of
    m_Width = wv;                           // data members
    m_Height = hv;
  }
 
  CBox() = default;                         // Default constructor
 
  // Function to calculate the volume of a box
  double volume() {  return m_Length* m_Width* m_Height;  }
};

The default keyword after the = specifies that the CBox no-arg constructor should be included in the class. Specifying it this way shows clearly that the default constructor is included in the class. You can see such a constructor in action.

Default Parameter Values

You have seen how you can specify default values for the parameters to a function in the function prototype. You use the same syntax for class member functions, including constructors. If you put the definition of the member function inside the class, you can put the default values for the parameters in the function header. If you include only the prototype of a function in the class definition, the default parameter values should go in the prototype, not in the function definition.

You could use this technique as an alternative to specifying initial values for the data members of the CBox class. You could alter the class in the last example to this:

class CBox                             // Class definition at global scope
{
public:
  double m_Length;                     // Length of a box in inches
  double m_Width;                      // Width of a box in inches
  double m_Height;                     // Height of a box in inches
        
  // Constructor definition
  CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0)
  {
    cout << "Constructor called." << endl;
    m_Length = lv;                     // Set values of
    m_Width = wv;                      // data members
    m_Height = hv;
  }
        
  // Default constructor definition
  CBox()
  {
    cout << "Default constructor called." << endl;
  }
        
  // Function to calculate the volume of a box
  double Volume()
  {
    return m_Length*m_Width*m_Height;
  }
};

If you make this change to the last example, what happens? You get another error message from the compiler, of course. Amongst a lot of other stuff, you get these useful comments from the compiler:

warning C4520: 'CBox': multiple default constructors specified
error C2668: 'CBox::CBox': ambiguous call to overloaded function

This is because the compiler can’t work out which of the two constructors to call — the one for which you have set default parameter values or the constructor that doesn’t accept any parameters. The definition of box2 requires a constructor without parameters and either constructor can now be called with no arguments. The obvious solution to this is to get rid of the constructor that has no parameters. This is actually beneficial. Without this constructor, any CBox object defined without being explicitly initialized will automatically have its members initialized to 1. Personally, I prefer to set up such default values as in-class member initializations, but let’s see how it works with default constructor argument values anyway.

Using a Constructor Initialization List

You can initialize data members using a constructor initialization list within the header in a constructor definition. This is different from using an initializer list when you call a constructor; the initializer list just contains the arguments to be passed to the constructor. I can demonstrate this with an alternative CBox constructor:

    // Constructor definition using an initialization list
    CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0):
                                    m_Length {lv}, m_Width {wv}, m_Height {hv}
    {
      cout << "Constructor called." << endl;
    }

This definition is written assuming that it is within the class definition. The constructor initializing list is separated from the parameter list by a colon, and the initializers for class members are separated by commas. Values of the data members are not set in assignment statements in the body of the constructor. They each have an initial value defined in an initializer list that is part of the function header. The members are effectively created in the constructor initializer list. The member m_Length is initialized by the value of the argument passed for the parameter, lv, for example. Functional notation also works, but it’s better to use the uniform initialization syntax. Initializing members in the constructor header is more efficient than using assignments as in the previous version. The member initialization list always executes before the body of the function so you can use the values of any members initialized in the list in the body of the constructor. If you substitute this constructor in Ex7_06.cpp, you will see that it works just as well.

With class members that are const or reference types, you have no choice as to how they are initialized. The only way is to use a member initializer list in a constructor. Assignment within the body of a constructor will not work. Note also that the members are not initialized in the order they are written in the constructor initializer list, but in the order in which they are defined in the class definition.

Making a Constructor Explicit

This discussion assumes that the CBox constructor with three parameters has no default parameter values specified. This is because the three-parameter constructor could function as a constructor with a single argument and as a constructor with two arguments because the parameters with no arguments specified would assume the default values.

If you define a constructor with a single parameter, the compiler will use this constructor for implicit conversions, which may not be what you want. For example, suppose you define a constructor for the CBox class like this:

CBox(double side): m_Length {side}, m_Width {side}, m_Height {side} {}

This constructor is handy when you want to define CBox objects that are cubes, where all three dimensions are the same. Because this constructor has a single parameter, the compiler will use it for implicit conversions from type double to type CBox when necessary. For example, consider the following:

CBox box;
box = 99.0;

The first statement calls the default constructor to create box, so a default constructor must be defined in the class. The second statement will call the constructor CBox(double) with the argument as 99.0, so you are getting an implicit conversion from a value of type double to type CBox. This may be what you want, but there will be many classes with single argument constructors where you don’t want this to happen. In these situations, you can use the explicit keyword in the definition of the constructor to prevent it:

explicit CBox(double side): m_Length {side}, m_Width {side}, m_Height {side} {}

With the constructor defined as explicit, it will only be called when it is explicitly invoked; it will not be used for implicit conversions. With the constructor defined as explicit, the statement assigning 99.0 to box will not compile. In general, unless you want your single-parameter constructors to be used for implicit-type conversions, it is best to define them all as explicit.

With the explicit CBox constructor in the previous fragment, this will not compile:

CBox box = 4.0;

The statement requires an implicit conversion from the value, 4.0, of type double, to type CBox and the compiler will not allow it. This will compile though:

CBox box {4.0};

This is not a conversion. This is an explicit call for the constructor with a single parameter.

There’s a more obscure way you can get accidental implicit conversions. In the previous version of the CBox class, the constructor has default values for all three parameters. This is how it looks using an initialization list in the header:

CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0):
                                m_Length {lv}, m_Width {wv}, m_Height {hv}
{
  cout << "Constructor called." << endl;
}

Because there are default values for the second and third parameters, the following statements will compile:

CBox box;
box = 99.0;

This time, you get an implicit call to the constructor with the first argument as 99.0, and the other two arguments will have default values. This is unlikely to be what you want. To prevent this, make the constructor explicit:

explicit CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0):
                                m_Length(lv), m_Width(wv), m_Height(hv)
{
  cout << "Constructor called." << endl;
}

Delegating Constructors

When a class has two or more constructors defined, one constructor can call another to help with creating an object, but only in the constructor initializer list in the constructor header, in which case there can be no other initializations within the list. Let’s look at an example to see how this works. Suppose you have defined the CBox class constructor as:

explicit CBoxCbox(double lv, double wv, double hv):
                                m_Length{lv}, m_Width{wv}, m_Height{hv}     {}

You can now define a constructor with a single parameter like this:

explicit CBox(double side): CBox {side, side, side}
{}

This calls the previous constructor in the member initializing list. This technique can save repeating code in several constructors, where some of the members are always initialized in the same way, for example, but others are initialized uniquely, depending on which constructor is used.

The only way one constructor can call another in a given class is through the member initializing list. You cannot call a CBox constructor to assist with creating an object in the body of a CBox constructor. Calling a constructor in the body of a constructor, or any function for that matter, is creating an independent object.

PRIVATE MEMBERS OF A CLASS

Having a constructor that sets the values of the data members of a class object but still allows any part of a program to mess with what are essentially the guts of the object, is almost a contradiction in terms. To draw an analogy, after you have arranged for a brilliant surgeon with skills honed over years of training to do things to your insides with a sharp knife, letting the local plumber or bricklayer have a go is a bit risky, to say the least. You need protection for your class data members.

You can get the security you need by using the private keyword when you define the class members. Data and function members of a class that are private can, in general, be accessed only by member functions of a class. There’s one exception, but we’ll worry about that later. A normal function has no direct means of accessing the private members of a class. This is shown in Figure 7-6.

image

FIGURE 7-6

Having the possibility of specifying some class members as private enables you to separate the interface to the class from its internal implementation. The interface to a class consists of the public data members and the public member functions. Where necessary, public member functions can provide indirect access to any member of a class, including the private members. By keeping the internals of a class private, you can later modify them to improve performance for example, without requiring changes to code that uses the class through its public interface. To keep the data and function members of a class safe from unnecessary meddling, it’s good practice to define those that don’t need to be exposed as private. Only make public what is essential to the use of your class.

Accessing private Class Members

On reflection, declaring the data members of a class as private is rather extreme. It’s all very well protecting them from unauthorized modification, but that’s no reason to keep their values a secret. What you need is a Freedom of Information Act for private members.

You don’t have to start writing to your state senator to get it — it’s already available. You can write a member function to return the value of a data member. Look at this member function for the CBox class:

inline double CBox::getLength()
{
  return m_Length;
}

Just to show how it looks, I have written this as a member function definition that is external to the class. I’ve specified it as inline because we’ll benefit from the speed increase without increasing the size of the code much. As I said earlier, this is not strictly necessary with Visual C++. With the definition of the function in the public section of the class, you can use it like this:

double len {box2.getLength()};         // Obtain data member length

You can write a similar function for each data member that you want to make available to the outside world, and their values can be accessed without prejudicing the security of the class. Of course, if the function definitions are within the class, they will be inline by default.

The friend Functions of a Class

There will be circumstances when you want certain selected functions that are not members of a class to be able to access all the members of a class — a sort of elite group with special privileges. Such functions are called friend functions of a class and are defined using the keyword friend. You can either include the prototype of a friend function in the class definition, or you can include the whole function definition. Functions that are friends of a class and are defined within the class definition are also inline by default.

Imagine that you want to implement a friend function in the CBox class to compute the surface area of a CBox object.

Placing friend Function Definitions Inside the Class

You could combine the definition of the function with its definition as a friend of the CBox class within the class definition, and the code would run as before. The function definition in the class would be:

friend double boxSurface(const CBox& aBox)
{
  return 2.0*(aBox.m_Length*aBox.m_Width +
              aBox.m_Length*aBox.m_Height +
              aBox.m_Height*aBox.m_Width);
}

However, this has a disadvantage. Although the function still has global scope, this might not be obvious to readers of the code, because the function is buried in the class definition.

The Default Copy Constructor

Suppose you define and initialize a CBox object like this:

CBox box1 {78.0, 24.0, 18.0};

You now want to create another CBox object, identical to box1. In other words you would like to initialize a new CBox object with box1. Let’s try it.

THE POINTER THIS

You wrote the volume() member of the CBox class in terms of the class member names in the definition of the class. Of course, every CBox object that you create contains their own independent set of these members, so there has to be a mechanism for the function to refer to the members of the particular object for which it is called.

When any member function executes, it always contains a hidden pointer with the name this, which contains the address of the object used to call the function. Therefore when the m_Length member name appears in the body of the volume() function, it’s actually this->m_Length, which is the fully specified reference to the member of the object that is used to call the function. The compiler takes care of adding this to the member names in the function.

You can use this explicitly within a member function if you need to — when you want to return a pointer to the current object, for example.

CONST OBJECTS

The volume() function in the CBox class doesn’t alter the object for which it is called; neither does a function such as getHeight() that returns the value of the m_Height member. Likewise, the compare() function in the previous example didn’t change the class objects. This may seem at first sight to be a mildly interesting but largely irrelevant observation, but it isn’t — it’s quite important. Let’s think about it.

You will undoubtedly want to create class objects that are fixed from time to time, just like values such as pi or inchesPerFoot that you might define as const. Suppose you wanted to define a CBox object as const — because it was a very important standard-sized box, for instance. You might define it with the following statement:

const CBox standard {3.0, 5.0, 8.0};

Now that you have defined your standard box having dimensions 3 × 5 × 8, you don’t want it changed. In particular, you don’t want to allow the values of its data members to be altered. How can you be sure they won’t be?

Well, you already are. If you define an object as const, the compiler will not allow a member function to be called for it that might alter it. You can demonstrate this quite easily by modifying the definition for the object, cigar, in the previous example to:

const CBox cigar {8.0, 5.0, 1.0};           // Declare cigar box

If you try to compile the program with this change, you will see the error message:

error C2662: 'CBox::compare' : cannot convert 'this' pointer
 
from 'const CBox' to 'CBox &' Conversion loses qualifiers

This is caused by the if statement that calls the compare() member of cigar. An object that you define as const will always have a this pointer that is const, so the compiler will not allow any member function to be called that does not assume the this pointer is const. You need to find out how to make the this pointer const in a member function.

const Member Functions of a Class

To make this in a member function const, you must define the function as const in the class definition. Here’s how you do that with the compare() and volume() members of CBox:

class CBox                             // Class definition at global scope
{
public:
  // Constructor definition
  explicit CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0) :
             m_Length {lv}, m_Width {wv}, m_Height {hv}  
  {
    cout << "Constructor called." << endl;
  }
        
  // Function to calculate the volume of a box
  double volume() const
  {
    return m_Length*m_Width*m_Height;
  }
        
  // Function to compare two boxes which returns true (1)
  // if the first is greater than the second, and false (0) otherwise
  bool compare(const CBox& xBox) const
  {
    return this->volume() > xBox.volume();
  }
        
private:
  double m_Length;                     // Length of a box in inches
  double m_Width;                      // Width of a box in inches
  double m_Height;                     // Height of a box in inches
};

You can find this code in the download as Ex7_10A.cpp. To specify that a member function is const, you append the const keyword to the function header. Note that you can only do this with functions that are class members. It does not apply to ordinary global functions. Declaring a function as const is only meaningful for a function that is a member of a class. The effect is to make the this pointer in the function const. This implies that a data member cannot appear on the left of an assignment within the function definition; it will be flagged as an error by the compiler. A const member function cannot call a non-const member function of the same class because this could potentially modify the object. This means that because compare()calls volume(), the volume() member must be defined as const too. With the volume() function defined as const, you can make the parameter to the compare() function const. When volume() was a non-const member of the class, making the compare() function parameter const would result in a C2662 error message from the compiler. When you define an object as const, the member functions that you call for it must be defined as const; otherwise, the program will not compile. The compare() function now works with const and non-const objects.

Member Function Definitions Outside the Class

When the definition of a const member function appears outside the class, the header for the definition must have the const keyword added, just as the definition within the class does. You should always define member functions that do not alter the class object for which they are called as const. With this in mind, the CBox class could be defined as:

class CBox                                 // Class definition at global scope
{
public:
  // Constructor
  explicit CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0);
        
  double volume() const;                   // Calculate the volume of a box
  bool compare(const CBox& xBox) const;    // Compare two boxes
        
private:
  double m_Length;                         // Length of a box in inches
  double m_Width;                          // Width of a box in inches
  double m_Height;                         // Height of a box in inches
};

This assumes that all function members, including the constructor, are defined outside the class. Both the volume() and compare() members have been defined as const. The volume() function is now defined as:

double CBox::volume() const
{
  return m_Length*m_Width*m_Height;
}

The compare() function definition is:

bool CBox::compare(const CBox& xBox) const
{
  return this->volume() > xBox.volume();
}

As you can see, the const modifier appears in both definitions. If you leave it out, the code will not compile. A function with a const modifier is a different function from one without, even though the name and parameters are exactly the same. Indeed, you can have both const and non-const versions of a function in a class, and sometimes this can be very useful.

With the class defined as shown, the constructor also needs to be defined separately, like this:

CBox::CBox(double lv, double wv, double hv):
                  m_Length {lv}, m_Width {wv}, m_Height {hv}
{
  cout << "Constructor called." << endl;
}

ARRAYS OF OBJECTS

You create an array of objects in exactly the same way as you created an array of elements of a fundamental type. Each element of an array of objects that you don’t initialize will cause the default constructor to be called.

STATIC MEMBERS OF A CLASS

Both data members and function members of a class can be defined as static. Because the context is a class, there’s a little more to it than the effect of the static keyword outside a class. We’ll look at static data members first.

Static Data Members

When you define a data member of a class to be static, only one instance of the static member is created and this is shared between all objects of the class. Each object gets its own copy of each of the ordinary data members, but only one instance of each static data member exists, regardless of how many class objects have been defined. Figure 7-7 illustrates this.

image

FIGURE 7-7

One use for a static data member is to count how many objects have been created. You could add a static data member to the public section of the CBox class by adding the following statement to the class definition:

    static int objectCount;                // Count of objects in existence

You now have a problem. How do you initialize the static data member?

You can’t initialize a static data member in the class definition unless it is const, and then only when it is a numeric or enumeration type. You don’t want to initialize objectCount in a constructor, because you want to increment it every time a constructor is called to accumulate the count of the number of objects created. You can’t initialize it in another member function because a member function is associated with an object, and you want it initialized before any objects are created. You can initialize a static data member outside of the class definition though, like this:

int CBox::objectCount {};         // Initialize static member of CBox class

Notice that the static keyword is not used in the initialization statement; however, you must qualify the member name by using the class name and the scope resolution operator so that the compiler understands that you are referring to a static member of the class. Otherwise, you would simply create a global variable that had nothing to do with the class.

Static Function Members of a Class

By declaring a function member as static, you make it independent of any objects of the class type. Consequently it does not have the pointer this. A static member function has the advantage that it exists, and can be called, even if no class objects exist. In this case, only static data members can be accessed because they are the only ones that exist. Thus, you can call a static function member of a class to examine static data members, even when you do not know for certain that any objects of the class exist. You could therefore use a static member function to determine whether any objects of the class have been created or how many have been created.

Here’s an example of a prototype for a static function:

static void aFunction(int n);

A static function can be called in relation to a particular object by a statement such as the following:

aBox.aFunction(10);

The function has no access to the non-static members of aBox. The same function could also be called without reference to an object. In this case, the statement would be:

CBox::aFunction(10);

The class name qualifies the function. Using the class name and the scope resolution operator tells the compiler to which class aFunction() belongs.

POINTERS AND REFERENCES TO OBJECTS

Using pointers and references to class objects, is very important in object-oriented programming, particularly in the specification of function parameters. Class objects can involve considerable amounts of data, so using the pass-by-value mechanism for them can be very time-consuming and inefficient because each object that is passed to a function in this way will be copied. Using reference parameters avoids this overhead and reference parameters are essential to some operations with classes. As you’ll see, it’s not possible to write a copy constructor without using a reference parameter.

Pointers to Objects

You define a pointer to an object in the same way that you define other pointers. For example, a pointer to objects of type CBox is defined in this statement:

CBox* pBox {};               // Declare a pointer to CBox - initialized to nullptr

You can use this to store the address of a CBox object in an assignment in the usual way, using the address operator:

pBox = &cigar;               // Store address of CBox object cigar in pBox

As you saw when you used the this pointer in the compare() member function, you can call a function using a pointer to an object. You can call the volume() function for the pointer pBox like this:

cout << pBox->volume();      // Display volume of object pointed to by pBox

This uses the indirect member selection operator. This is the typical notation used for this kind of operation, so from now on, I’ll use it universally.

References to Class Objects

References really come into their own when they are used with classes. As with pointers, there is no difference between the way you define and use references to class objects and the way in which you have already defined and used references to variables of primitive types. To define a reference to the object cigar, for instance, you would write this:

CBox& rcigar {cigar};                  // Define reference to object cigar

To use a reference to calculate the volume of the object cigar, you would just use the reference name where the object name would otherwise appear:

cout << rcigar.volume();               // Output volume of cigar thru a reference

As you may remember, a reference acts as an alias for the object it refers to, so the usage is exactly the same as using the original object name.

Implementing a Copy Constructor

References are of major importance in the context of parameters and return values in functions, particularly class member functions. Let’s return to the copy constructor as a first toe in the water. For the moment, I’ll sidestep the question of when you need to write your own copy constructor and concentrate on the problem of how you write it. I’ll use the CBox class to make the discussion more concrete.

The copy constructor is a constructor that creates a new object from an existing object of the same type. It therefore needs to accept an object of the class as an argument. You might consider writing the prototype like this:

CBox(CBox initB);

Now, think about what happens when this constructor is called. Suppose you write this definition:

CBox myBox {cigar};

This generates a call of the copy constructor. This seems to be no problem, until you realize that the argument is passed by value. So, before cigar can be passed to the constructor, the compiler needs to arrange to make a copy of it. Naturally, it calls the copy constructor to make a copy of the argument to the copy constructor. Unfortunately, since it is passed by value, this call also needs a copy of its argument to be made, so the copy constructor is called again, and so on and so on. You end up with an infinite number of calls to the copy constructor.

The solution, as I’m sure you’ll have guessed, is to use a const reference parameter. You can write the prototype of the copy constructor like this:

CBox(const CBox& initB);

Now, the argument to the copy constructor doesn’t need to be copied. The argument is used to initialize the reference parameter so no copying takes place. As you remember from the discussion on references, if a parameter to a function is a reference, no copying of the argument occurs when the function is called. The function accesses the argument in the caller function directly. The const qualifier ensures that the argument can’t be modified by the function.

You could implement the copy constructor as follows:

CBox::CBox(const CBox& initB) : 
     m_Length {initB.m_Length}, m_Width {initB.m_Width}, m_Height {initB.m_Height}
{}

This definition assumes that it appears outside of the class definition. The constructor name is qualified with the class name using the scope resolution operator. Each data member of the object being created is initialized with the corresponding member of the object passed as the argument in the member initialization list.

The CBox class is not an example of when you need to write a copy constructor. As you have seen, the default copy constructor works perfectly well with CBox objects. I will get to why and when you need to write your own copy constructor in the next chapter.

SUMMARY

You now understand the basic ideas of classes. You’re going to see more and more about using classes throughout the rest of the book.

EXERCISES

  1. Define a struct Sample that contains two integer data items. Write a program that defines two objects of type Sample, called a and b. Set values for the data items that belong to a and then check that you can copy the values into b by simple assignment.
  2. Add a char* member to the Sample struct in the previous exercise called sPtr. When you fill in the data for a, dynamically create a string buffer initialized with "Hello World!" and make a.sPtr point to it. Copy a into b. What happens when you change the contents of the character buffer pointed to by a.sPtr and then output the contents of the string pointed to by b.sPtr? Explain what is happening. How would you get around this?
  3. Create a function that accepts a pointer to an object of type Sample as an argument and that outputs the values of the members of the object that is passed to it. Test this function by extending the program that you created for the previous exercise.
  4. Define a class CRecord with two private data members that store a name up to 14 characters long and an integer item number. Define a getRecord() function member of the CRecord class that will set values for the data members by reading input from the keyboard, and a putRecord() function member that outputs the values of the data members. Implement the getRecord() function so that a calling program can detect when a zero item-number is entered. Test your CRecord class with a main() function that reads and outputs CRecord objects until a zero item-number is entered.
  5. Define a class to represent a push-down stack of integers. A stack is a list of items that permits adding (“pushing”) or removing (“popping”) items only from one end and works on a last-in, first-out principle. For example, if the stack contained [10 4 16 20], pop() would return 10, and the stack would then contain [4 16 20]; a subsequent push(13) would leave the stack as [13 4 16 20]. You can’t get at an item that is not at the top without first popping the ones above it. Your class should implement push() and pop() functions, plus a print() function so that you can check the stack contents. Store the list internally as an array, for now. Write a test program to verify the correct operation of your class.
  6. What happens with your solution to the previous exercise if you try to pop more items than you’ve pushed, or save more items than you have space for? Can you think of a robust way to trap this? Sometimes, you might want to look at the number at the top of the stack without removing it; implement a peek() function to do this.

WHAT YOU LEARNED IN THIS CHAPTER

TOPIC CONCEPT
Classes A class provides a means of defining your own data types. They can reflect whatever types of objects your particular problem requires.
Class members A class can contain data members and function members. The function members of a class always have free access to the data members of the same class.
Class constructors Objects of a class are created and initialized using member functions called constructors. A constructor is called automatically when you define an object. Constructors may be overloaded to provide different ways of initializing an object.
The default constructor The compiler will supply a default constructor in a class that has no constructors defined. The default constructor has no parameters and does nothing. Defining any constructor in a class inhibits the insertion of the default constructor so if you need it, you must specify it.
Explicit constructors Constructors specified using the explicit keyword can only be called explicitly, and therefore cannot be used for implicit conversions from another type. This is only relevant for one-arg constructors and constructors with multiple default arguments.
Member initializer list Class members can be initialized in a member initializer list in the constructor header. Initializing members in this way is more efficient than using assignments in the body of the constructor.
Delegating constructors A constructor can call another constructor in the same class, but only in its constructor initializer list. The constructor call must be the only thing in the initializer list.
Class member access Members of a class can be specified as public, in which case, they are freely accessible by any function in a program. Members may be specified as private, in which case, they may only be accessed by member functions or friend functions of the class.
Static class members Members of a class can be defined as static. Only one instance of each static data member of a class exists, which is shared amongst all instances of the class, no matter how many objects of the class are created.
Static function members have no this pointer.
The pointer this Every non-static function of a class contains the pointer this, which points to the object for which the function was called.
const member functions A member function that is defined as const has a const this pointer, and therefore cannot modify data members of the object for which it is called.
Calling const member functions A member function that is defined as const cannot call another member function that is not const. You cannot call a non-const member function for a const object.
Passing objects to a function by reference Using references to class objects as arguments to function calls can avoid substantial overhead in passing complex objects to a function.
The copy constructor A copy constructor duplicates an existing object of the same class type. A copy constructor always has its parameter specified as a const reference.
..................Content has been hidden....................

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