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.
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.
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.
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
.
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.
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
.
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.
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.
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.
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.
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.
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, ->
, 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.
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 struct
s 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.
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.
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
.
I’ll first summarize some of the terminology that I will be using when discussing classes:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
NOTE Of course, the compiler ensures that expanding a function inline doesn’t cause any problems with variable name conflicts or scope.
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.
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.
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.
Let’s extend our CBox
class to incorporate a constructor:
// Ex7_04.cpp
// Using a constructor
#include <iostream>
using std::cout;
using std::endl;
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;
}
// Function to calculate the volume of a box
double volume()
{
return m_Length* m_Width* m_Height;
}
};
int main()
{
CBox box1 {78.0,24.0,18.0}; // Declare and initialize box1
CBox cigarBox {8.0,5.0,1.0}; // Declare and initialize cigarBox
cout << "Volume of box1 = " << box1.volume() << endl;
cout << "Volume of cigarBox = " << cigarBox.volume() << endl;
return 0;
}
The CBox()
constructor has three parameters of type double
, corresponding to the m_Length, m_Width
, and m_Height
members of an object. The first statement in the constructor outputs a message so that you can tell when it’s been called. You wouldn’t do this in production programs, but it’s very helpful in showing when a constructor is called, so it’s often used when testing a program. I’ll use it regularly for the purposes of illustration. The code in the body of the constructor is very simple. It just assigns the arguments that you pass to the constructor when you call it to the corresponding data members. If necessary, you could also include checks that valid, non-negative arguments are supplied and in a real context you probably would do this, but our primary interest here is in seeing how the mechanism works.
Within main()
, you define the box1
object with values for the data members m_Length, m_Width
, and m_Height
, in sequence:
CBox box1 {78.0,24.0,18.0}; // Declare and initialize box1
The constructor arguments are in an initializer list following the object name. You can also use functional notation to call the constructor:
CBox box1(78.0,24.0,18.0); // Declare and initialize box1
This is older syntax so you will still see it used, but it’s better to use an initializer list because almost everything can be initialized in this way.
Note that calling a constructor is quite different from the statements earlier where you supplied an initializer list containing values for public data members. There you could have fewer values in the list and members would be initialized with zero. Here the initializer list contains the arguments for the constructor. There are three parameters so three values must appear in the list.
You create a second CBox
object, called cigarBox
, by calling the constructor.
The volume of box1
is calculated by calling its volume()
member as in the previous example, and is then output. You also output the value of the volume of cigarBox
. The output from the example is:
Constructor called.
Constructor called.
Volume of box1 = 33696
Volume of cigarBox = 40
The first two lines are from the calls of the CBox
constructor, once for each object created. The constructor that you have supplied is automatically called when a CBox
object is defined, so the CBox
objects have members with the values appearing in the initializer lists for the constructor calls. The values are passed to the constructor as arguments in the sequence that they are written in the list. As you can see, the volume of box1
is the same as before and cigarBox
has a volume looking suspiciously like the product of its dimensions, which is quite a relief.
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.
Let’s add a default constructor to the last example, along with the definition for box2
, plus the original assignments for the data members of box2
. You can enlarge the default constructor just enough to show that it is called. Here is the next version of the program:
// Ex7_05.cpp
// Supplying and using a default constructor
#include <iostream>
using std::cout;
using std::endl;
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;
}
// 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;
}
};
int main()
{
CBox box1{ 78.0, 24.0, 18.0 }; // Define and initialize box1
CBox box2; // Define box2 - no initial values
CBox cigarBox{ 8.0, 5.0, 1.0 }; // Define and initialize cigarBox
cout << "Volume of box1 = " << box1.volume() << endl;;
cout << "Volume of cigarBox = " << cigarBox.volume() << endl;;
box2.m_Height = box1.m_Height - 10; // Define box2
box2.m_Length = box1.m_Length / 2.0; // members in
box2.m_Width = 0.25*box1.m_Length; // terms of box1
cout << "Volume of box2 = " << box2.volume() << endl;
return 0;
}
Now that you have included your own version of the default constructor, there are no error messages from the compiler and everything works. The program produces this output:
Constructor called.
Default constructor called.
Constructor called.
Volume of box1 = 33696
Volume of cigarBox = 40
Volume of box2 = 6084
The default constructor just displays a message. Evidently, it was called when you defined the object box2
. You also get the correct value for the volumes of all three CBox
objects, so the rest of the program is working as it should.
I included the full definition of the default constructor in the class so you can see when it is called. If you want to see it working using the default keyword, replace the definition with:
CBox() = default;
You now know that you can overload constructors just as you overloaded functions in Chapter 6. You have just executed an example with two constructors that differ only in their parameter list. One has three parameters of type double
and the other has no parameters at all.
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.
You can demonstrate this with the following simplified example:
// Ex7_06.cpp
// Supplying default values for constructor arguments
#include <iostream>
using std::cout;
using std::endl;
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;
}
// Function to calculate the volume of a box
double volume()
{
return m_Length*m_Width*m_Height;
}
};
int main()
{
CBox box2; // Declare box2 - no initial values
cout << "Volume of box2 = " << box2.volume() << endl;
return 0;
}
You only define a single uninitialized CBox
variable — box2
— because that’s all you need for demonstration purposes. This version of the program produces the following output:
Constructor called.
Volume of box2 = 1
This shows that the constructor with default parameter values is doing its job of setting the values of objects that have no initializing values specified.
You should not assume from this that this is the only, or even the recommended, way of implementing the default constructor. There will be many occasions where you won’t want to assign default values in this way, in which case, you’ll need to write a separate default constructor. There will even be times when you don’t want to have a default constructor operating at all, even though you have defined another constructor.
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.
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;
}
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.
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.
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.
You can rewrite the CBox
class to make its data members private
.
// Ex7_07.cpp
// A class with private members
#include <iostream>
using std::cout;
using std::endl;
class CBox // Class definition at global scope
{
public:
// Constructor definition using an initialization list
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()
{
return m_Length*m_Width*m_Height;
}
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
};
int main()
{
CBox match {2.2, 1.1, 0.5}; // Declare match box
CBox box2; // Declare box2 - no initial values
cout << "Volume of match = " << match.volume() << endl;
// Uncomment the following line to get an error
// box2.m_Length = 4.0;
cout << "Volume of box2 = " << box2.volume() << endl;
return 0;
}
The CBox
constructor is explicit
to prevent implicit conversions. The CBox
class definition now has two sections. The first is the public
section containing the constructor and the member function, volume()
. The second section is specified as private
and contains the data members. The data members can only be accessed by the member functions of the class. You don’t have to modify any of the member functions — they can access all the data members of the class anyway. If you uncomment the statement in main()
that assigns a value to the m_Length
member of the box2
object, you’ll get a compiler error message confirming that the data member is inaccessible. If you haven’t already done so, take a look at the members of the CBox
class in Class View; the icon alongside a member indicates its accessibility; a small padlock in the icon shows when a member is private
.
A point to remember is that using a constructor or a member function is the only way to get a value into a private
data member of an object. You have to make sure that all the ways in which you might want to set or modify private
data members of a class are provided for through member functions.
You can put functions in the private
section of a class. In this case, they can only be called by other member functions. If you put the volume()
function in the private
section, you will get compiler errors for the statements that attempt to use it in main()
. If you put the constructor in the private
section, you won’t be able to create any objects of the class type.
The example generates this output:
Constructor called.
Constructor called.
Volume of match = 1.21
Volume of box2 = 1
The output demonstrates that the class is still working satisfactorily, with its data members defined as private
. The major difference is that they are now completely protected from unauthorized access and modification.
If you don’t specify otherwise, the default access attribute for class members is private
. You could put all your private
members at the beginning of the class definition and let them default to private
. It’s better, however, to explicitly state the access attribute in every case, so there is no doubt about what you intend.
Of course, you don’t have to make all data members private
. If the application for your class requires it, you can have some data members defined as private
and some as public
. It depends on what you’re trying to do. If there’s no reason to make members public
, it’s better to make them private
because it makes the class more secure. Ordinary functions can’t access the private
members of your class.
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.
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.
NOTE Friend functions are not members of the class, so the access attributes do not apply to them. They are just ordinary global functions with special privileges.
Imagine that you want to implement a friend function in the CBox
class to compute the surface area of a CBox
object.
You can see how a friend
function works in the following example:
// Ex7_08.cpp
// Creating a friend function of a class
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
using std::setw;
class CBox // Class definition at global scope
{
public:
// Constructor definition
Box(double lv, double wv, double hv) :
m_Length{ lv }, m_Width{ wv }, m_Height{ hv }
{ cout << "3-arg Constructor called." << endl; }
explicit CBox(double side) : CBox{ side, side, side }
{ cout << "1-arg Constructor called." << endl; }
CBox() = default;
// Function to calculate the volume of a box
double volume()
{
return m_Length*m_Width*m_Height;
}
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
// Friend function
friend double boxSurface(const CBox& aBox);
};
// friend function to calculate the surface area of a Box object
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);
}
int main()
{
CBox match{ 2.2, 1.1, 0.5 }; // match box using 3-arg constructor
CBox cube{ 5.0 }; // Define cube using 1-arg constructor
CBox box; // Define box using default constructor
cout << "Volume of match =" << setw(10) << match.volume()
<< " Surface area = " << boxSurface(match) << endl;
cout << setw(16) << "Volume of cube =" << setw(10) << cube.volume()
<< " Surface area = " << boxSurface(cube) << endl;
cout << "Volume of box =" << setw(10) << box.volume()
<< " Surface area = " << boxSurface(box) << endl;
return 0;
}
This example also calls a constructor in the member initializer list and specifies that the default constructor should be included using the default
keyword. The output is something like:
3-arg Constructor called.
3-arg Constructor called.
1-arg Constructor called.
Volume of match = 1.21 Surface area = 8.14
Volume of cube = 125 Surface area = 150
Volume of box =-7.92985e+185 Surface area = 5.14037e+124
You define the boxSurface()
function as a friend of the CBox
class by writing the function prototype in the class definition with the keyword friend
at the front. The boxSurface()
function is global, so it makes no difference where you put the friend
definition within the class definition. It’s a good idea to be consistent where you position this sort of definition, though. I chose to position it after all the public
and private
members of the class. Remember, a friend function isn’t a class member, so access attributes don’t apply.
The friend
function definition follows that of the class. You specify access to the data members of the object within the definition of boxSurface()
, using the CBox
object specified by the parameter. Because a friend function isn’t a class member, the data members can’t be referenced just by their names. They each have to be qualified by an object name in exactly the same way as an ordinary function accesses public members. A friend function is the same as an ordinary function, except that it can access all the members of the class for which it is a friend, without restriction.
The output should be exactly what you would expect. The first line of output is from the constructor that creates the match
object. The second line of output is from the call of the three-arg constructor in the constructor initializer list of the one-arg constructor. The third line of output is from the body of the one-arg constructor so this demonstrates that the member initializer list executes before the body of a constructor.
The friend function is computing the surface area of CBox
objects from the values of the private
data members. In the case of the box
object created by the default constructor, the dimensions are junk values so the volume and surface area are also junk.
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.
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 following example shows this in action:
// Ex7_09.cpp
// Initializing an object with an object of the same class
#include <iostream>
using std::cout;
using std::endl;
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()
{
return m_Length*m_Width*m_Height;
}
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
};
int main()
{
CBox box1 {78.0, 24.0, 18.0};
CBox box2 {box1}; // Initialize box2 with box1
cout << "box1 volume = " << box1.volume() << endl
<< "box2 volume = " << box2.volume() << endl;
return 0;
}
This example produces the following output:
Constructor called.
box1 volume = 33696
box2 volume = 33696
The program is working exactly as you would want, with both boxes having the same volume. However, as you can see from the output, our constructor was called only once — to create box1
. So how was box2
created?
The mechanism is similar to when you had no constructor defined. The compiler supplied a default constructor to allow an object to be created. In this instance, the compiler generates a default version of what is referred to as a copy constructor.
A copy constructor does exactly what we’re doing here — it creates a new object that is a copy of an existing object of the same type. The default version of the copy constructor creates a new object by copying the existing object, member by member.
This is fine for simple classes such as CBox
, but for many classes — classes that have pointers as members, for example — the default copy constructor won’t work as you would want. Indeed, with such classes the member-by-member copying that is done by the default copy constructor can create serious errors in your program. In these cases, you must create your own copy constructor, which will replace the default version. This requires a special approach that you’ll look into more fully toward the end of this chapter and again in the next chapter.
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.
You can add a public
function to the CBox
class that compares the volumes of two CBox
objects:
// Ex7_10.cpp
// Using the pointer this
#include <iostream>
using std::cout;
using std::endl;
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()
{
return m_Length*m_Width*m_Height;
}
// Function to compare two boxes which returns true
// if the first is greater than the second, and false otherwise
bool compare(CBox& xBox)
{
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
};
int main()
{
CBox match {2.2, 1.1, 0.5}; // Define match box
CBox cigar {8.0, 5.0, 1.0}; // Define cigar box
if(cigar.compare(match))
cout << "match is smaller than cigar" << endl;
else
cout << "match is equal to or larger than cigar" << endl;
return 0;
}
The compare()
member returns true
if the CBox
object for which the member function is called has a greater volume than the CBox
object passed as the argument, and false
if it doesn’t. The parameter to compare()
is a reference type to avoid unnecessary copying of the argument. In the return
statement, the prefixed object is referred to through the pointer this
, using the indirect member access operator, ->
.
Remember that you use the direct member access operator when accessing members through objects and the indirect member access operator when accessing members through pointers to objects. this
is a pointer, so you use the ->
operator.
The ->
operator works the same for pointers to class objects as it did when you were dealing with a struct
. Using this
here demonstrates that it exists and does work, but it’s quite unnecessary to use it explicitly in this case. You could change the return
statement in compare()
to:
return volume() > xBox.volume();
You’ll find that the program works just as well. Any references to unadorned member names are automatically the members of the object pointed to by this
.
You use the compare()
function in main()
to check the relationship between the volumes of the objects match
and cigar
. The output is:
Constructor called.
Constructor called.
match is smaller than cigar
This confirms that the cigar
object is larger than the match
object.
It wasn’t essential to define the compare()
function as a class member. You could just as well have written it as an ordinary function with the objects as arguments. Note that this isn’t true of the volume()
function because it needs to access the private
data members of the class. Of course, if you implemented compare()
as an ordinary function, it wouldn’t have access to the pointer this
, but it would still be very simple:
// Comparing two CBox objects - ordinary function version
bool compare(CBox& box1, CBox& box2)
{
return box1.volume() > box2.volume();
}
This has two CBox
objects as arguments and returns true
if the volume of the first is greater than the second. You could use this function to perform the same function as in the last example with this statement:
if(compare(cigar, match))
cout << "match is smaller than cigar" << endl;
else
cout << "match is equal to or larger than cigar" << endl;
If anything this looks slightly better and easier to read than the original; however, there’s a much better way to do this, which you’ll learn about in the next chapter.
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.
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.
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;
}
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.
The definition of CBox
in this example includes a specific default constructor:
// Ex7_11.cpp
// Using an array of class objects
#include <iostream>
using std::cout;
using std::endl;
class CBox // Class definition at global scope
{
public:
// Constructor definition
explicit CBox(double lv, double wv = 1.0, double hv = 1.0) :
m_Length{ lv }, m_Width{ wv }, m_Height{ hv }
{
cout << "Constructor called." << endl;
}
CBox() // Default constructor
{
cout << "Default constructor called." << endl;
m_Length = m_Width = m_Height = 1.0;
}
// Function to calculate the volume of a box
double volume() const
{
return m_Length*m_Width*m_Height;
}
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
};
int main()
{
CBox boxes[5]; // Array of CBox objects defined
CBox cigar(8.0, 5.0, 1.0); // Define cigar box
cout << "Volume of boxes[3] = " << boxes[3].volume()<< endl
<< "Volume of cigar = " << cigar.volume() << endl;
return 0;
}
The program produces this output:
Default constructor called.
Default constructor called.
Default constructor called.
Default constructor called.
Default constructor called.
Constructor called.
Volume of boxes[3] = 1
Volume of cigar = 40
You have modified the constructor that accepts arguments so that only two default values are supplied, and you have added a default constructor that initializes the data members to 1 after displaying a message that it was called. You are now able to see which constructor was called when. The constructors now have quite distinct parameter lists, so there’s no possibility of the compiler confusing them.
You can see from the output that the default constructor was called five times, once for each element of the boxes
array. The other constructor was called to create the cigar
object. It’s clear from the output that the default constructor initialization is working satisfactorily, as the volume of the array element boxes[3]
is 1.
Of course, you can initialize an array of objects when you define it. For example:
CBox boxes[5] {CBox {1,2,3},CBox {1,3,2}};
The initial values are defined in an initializer list. The first two elements will be initialized by calling the constructor with three arguments. The remaining three elements will use the default constructor.
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.
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.
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.
Let’s add the static
data member and the object-counting capability to the previous example.
// Ex7_12.cpp
// Using a static member to count objects
#include <iostream>
using std::cout;
using std::endl;
class CBox // Class definition at global scope
{
public:
static int objectCount; // Count of objects in existence
// Constructor definition
explicit CBox(double lv, double wv = 1.0, double hv = 1.0) :
m_Length{ lv }, m_Width{ wv }, m_Height{ hv }
{
cout << "Constructor called." << endl;
objectCount++;
}
CBox() // Default constructor
{
cout << "Default constructor called." << endl;
m_Length = m_Width = m_Height = 1.0;
objectCount++;
}
// Function to calculate the volume of a box
double volume() const
{
return m_Length*m_Width*m_Height;
}
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
};
int CBox::objectCount {}; // Initialize static member of CBox class
int main()
{
CBox boxes[5]; // Array of CBox objects defined
CBox cigar{ 8.0, 5.0, 1.0 }; // Declare cigar box
cout << "Number of objects (accessed through class) = "
<< CBox::objectCount << endl;
cout << "Number of objects (accessed through object) = "
<< boxes[2].objectCount << endl;
return 0;
}
This example produces the following output:
Default constructor called.
Default constructor called.
Default constructor called.
Default constructor called.
Default constructor called.
Constructor called.
Number of objects (through class) = 6
Number of objects (through object) = 6
This code shows that it doesn’t matter how you refer to the static
member objectCount
, whether through the class itself or any of the objects of that class. The value is the same and it is equal to the number of objects that have been created. The six objects are the five elements of the boxes
array, plus the cigar
object. It’s interesting to note that static
members of a class exist even though there may be no instances of the class defined. This is evidently the case, because you initialized the static
member objectCount
before any class objects were defined.
NOTE Static data members are automatically created when your program begins, and they will be initialized with 0 or its equivalent unless you initialize them with some other value. Thus, you need only to initialize static
data members of a class if you want them to start out with a value other than 0. However, you still need to define them. For example:
int CBox::objectCount;
This defines objectCount
but does not initialize it explicitly so the value will be 0 by default.
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.
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.
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.
Let’s try exercising the indirect member access operator a little more. We will use the example Ex7_10.cpp
as a base, but change it a little:
// Ex7_13.cpp
// Exercising the indirect member access operator
#include <iostream>
using std::cout;
using std::endl;
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
// if the first is greater than the second, and false otherwise
bool compare(const CBox* pBox) const
{
if(!pBox)
return false;
return this->volume() > pBox->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
};
int main()
{
CBox boxes[5]; // Array of CBox objects defined
CBox match {2.2, 1.1, 0.5}; // Declare match box
CBox cigar {8.0, 5.0, 1.0}; // Declare cigar Box
CBox* pB1 {&cigar}; // Initialize pointer to cigar object address
CBox* pB2 {}; // Pointer to CBox initialized to nullptr
cout << "Address of cigar is " << pB1 << endl // Display address
<< "Volume of cigar is " << pB1->volume() // Volume of object pointed to
<< endl;
pB2 = &match;
if(pB2->compare(pB1)) // Compare via pointers
cout << "match is greater than cigar" << endl;
else
cout << "match is less than or equal to cigar" << endl;
pB1 = boxes; // Set to address of array
boxes[2] = match; // Set 3rd element to match
// Now access through pointer
cout << "Volume of boxes[2] is " << (pB1 + 2)->volume() << endl;
return 0;
}
When you run the example, the output looks something like this:
Constructor called.
Constructor called.
Constructor called.
Constructor called.
Constructor called.
Constructor called.
Constructor called.
Address of cigar is 00B3FA5C
Volume of cigar is 40
match is less than or equal to cigar
Volume of boxes[2] is 1.21
The address for cigar
is likely to be different on your PC.
The only change to the class isn’t of great substance. I modified the compare()
function to accept a pointer to a CBox
object as an argument. You have an if
statement in the function that guards against the possibility for the argument to be nullptr. compare()
is defined as const
because it doesn’t alter the object. The main()
function merely exercises pointers to CBox
type objects in various, rather arbitrary, ways.
Within main()
, you define two pointers to CBox
objects after declaring an array, boxes
, and the CBox
objects cigar
and match
. The first, pB1
, is initialized with the address of the cigar
object, and the second, pB2
, is initialized to nullptr
. All of this uses the pointer in exactly the same way you would use a pointer to a basic type. The fact that you are using a pointer to a class type makes no difference.
You use pB1
with the indirect member access operator to generate the volume of the object pointed to, and the result is displayed. You then assign the address of match
to pB2
and use both pointers in calling the compare function. Because the parameter for compare()
is a pointer to a CBox
object, the function uses the indirect member selection operator to call the volume()
function for the argument.
To demonstrate that you can use address arithmetic on the pointer pB1
when using it to select a member function, you set pB1
to the address of the first element of the CBox
array, boxes
. In this case, you select the third element of the array and calculate its volume. This is the same as the volume of match
.
You can see from the output that there were seven constructor calls, five for the boxes
array, plus one each for the objects cigar
and match
. Overall, there’s virtually no difference between using a pointer to a class object and using a pointer to a primitive type, such as double
.
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.
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.
NOTE This is another important use of the const
qualifier. You should always define a reference parameter of a function as const
unless the function will modify it.
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.
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.
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.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?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.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.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.peek()
function to do this.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. |
3.143.235.23