CS202: Programming Systems
Lecture Notes #2
Building a C++ Foundation
with
Data
Abstraction
(the
syntax of classes)
Lecture
and Reference
The Concept of Classes and Objects
The basic function of a class is to define a new type of data. Once a class has been defined, instances of this class can be created. These instances are called objects. Software that uses a class to create and use objects is called a client program. Or, more simply, a client is a program that uses a class.
Think of an object as a variable who's type is specified by a class. Just as there can be zero or more variables of a built-in data type, there can be zero or more objects, or instances, of a class. A class defines the characteristics shared by all of its instances. The only difference between one instance of a class and another are the values of their data (i.e., their state).
The following sections explain the practical usage of classes. Classes represent our abstractions whereas objects represent instances of those abstractions. We will learn that classes have members that can be used to define an instance's state and behavior. And finally, we will learn about the relationship between classes and structures.
2.1 Classes are Abstractions; Objects are Instances
Class: An
abstraction representing a user defined data type that defines structure and behavior.
Object: An instance
of a class that gets allocated and deallocated.
A class is an abstraction that defines a grouping of attributes and operations. The attributes are represented by data and specify how objects will appear when they are created. The operations are represented by functions and specify the actions objects can perform. We actually bind our data and operations together in a capsule-like form; that is why we call it encapsulation!
An object is an instance of a class. An object is an entity that comes into existence at some moment in time and has memory allocated to it. Think of an object as existing in both space and time. It is a variable that gets allocated and deallocated.
The class itself does not create objects. Rather, a class is a specification or an abstraction of a real object. It allows a set of objects to later be defined that have the same structure and behavior.
If classes are designed in the form of libraries, classes can be used that have been developed by colleagues, developed by ourselves, or even purchased. We shouldn't need to understand how a class is implemented in order to use that class. Knowing the details of the implementation is unnecessary, if the class was properly designed. We only need to know how to use each class, such as what operations are available, what information to supply to those operations, and whether or not those operations may be used in larger expressions.
Practical Rule: Think of a class as a collection of components that can be accessed in a controlled way. If a class is designed in a way that requires clients to know how it does its job, then it wasn't designed correctly!
2.2 Classes have Members
Member: Data and operations that are defined as part of a
class.
Data Member: The data that is specified as part of a class.
Member Function: The operations (or methods) that can be
performed on each object.
Classes have members that represent each object's attributes and operations. These members consist of both data members and member functions. Data members represent the data that is associated with each object. Member functions represent the operations that can be performed on each object. Memory for data members is allocated when an object is defined. Once defined, member functions may be used to access and/or modify that data. It is important to realize that memory is not allocated for data members when the class itself is defined.
When we specify a class, we can define certain access privileges for data members and member functions. For example, we can restrict access to an object's data members to the functions defined as members of the class. By doing this, we hide the data and minimize unwanted side effects.
2.3 Classes versus Structures
A class is simply a generalized structure that encapsulates both data and functions as members. It provides built-in access control, protecting data and functions from unauthorized use. This is analogous to hiding data by declaring static global variables in a module. In reality, a class is the same as a structure except in the default conditions; in a class everything is hidden from the client.
When considering whether to use a class or a structure to represent abstractions, we recommend using structures when our goal is to combine multiple fundamental and/or compound data types together. Structures are useful for associating related data being used in an application. On the other hand, we recommend using classes when our goal is to define user defined data types consisting of both data and operations. In this light, classes can be used to extend the language and structures can be used to help organized our data.
2.4 The Class Interface
Class Interface: Declares the data and operations for a user defined
type. Also known as a class definition.
The class interface declares the data and operations that comprise objects of a class. The class interface is responsible for two main tasks: to specify the details of how that class can be used by a client and to specify the data and operations internal to the class. This means that the class interface must include the details of how a class looks to those who access it, listing the functions that can be used and the information that can be accessed from outside of that class. In addition, the class interface must declare all constants, objects, and operations that apply to that class, whether or not the client program has access to them. This is necessary because C++ needs to know how much memory to allocate when an object of that class is created.
The following sections introduce the syntax of the class interface, describe how access to data and operations can be controlled, and demonstrate a simple class interface. The purpose of these sections is to become familiar with defining classes. Once a class is defined, we can create objects of that class.
We will also learn how to separate a class' user interface from the actual implementation. This is useful because the class interface is usually kept in an interface file, included in the client program.
2.4.1 The Syntax of a Class Interface
The syntax of a class interface and a structure
is the same except for the keyword (i.e., class
versus struct). A class interface has
the following syntax. The body of the class is from the beginning opening brace
to the closing brace, which is followed by a semicolon.
class class_name {
public:
//Public data and operations are placed after the
//public keyword.
protected:
//Protected data and operations are placed after the
//protected keyword.
private:
//Private data and operations are placed after the
//private keyword.
};
Members can be placed in one of three sections: public, protected, or private. These sections are used to control the accessibility of those members from outside of the class and may be placed in any order. By default, all members of a class are private. By default, all members of a structure are public. For style purposes, the public section is listed first so we can quickly and easily determine which members are accessible by clients. Protected and private sections come next.
2.4.2 Class Scope
Class
Scope: The context in which members of a class are visible.
The heart of encapsulation is class scope. Class scope refers to those regions of a program where members of a class are visible. All members declared in the body of a class are defined to have class scope. This guarantees that they are visible and accessible by all member functions within that class. Class scope includes the class interface but also extends beyond the class interface to include the body of all member functions (i.e., the implementation of the member functions).
There are three different contexts that apply to class scope: members declared in the body of a class, members accessed from outside of a class' body, and members defined outside of a class but within class scope.
2.4.2.1 Class Scope
versus Local, Function, and Global Scope
Remember that scope determines where an identifier that has been defined or declared can be used. With local scope, identifiers are visible from the point they are defined until the end of the current block. With function scope, identifiers are visible anywhere within the function. With global scope, identifiers are visible from the point they are defined until the end of the source file. Figure 2-1 reminds us of each of these.

Figure 2-1: Local, Function, and Global Scope
Our classes are commonly defined as global, which means the class' identifier (the user defined data type) has global scope. However, classes may also be defined or declared local, which means the class' identifier has local scope. This follows the same guidelines as were used for structure identifiers.
By now, we should be very familiar with the differences between local (or block), function, and global scope. But, now that we have introduced one additional scope category (class scope), we need to understand how this scope relates to those that we are familiar with. With class scope, identifiers representing members are visible all throughout the class interface and in all member functions. Therefore, within a member function, we can access locals to that function, identifiers that have function scope, and members of its class. All locals defined within a member function have precedence. Therefore, if a local has the same name as a member or a global, the local takes precedence and hides the corresponding member or global. Figure 2-2 shows how class scope fits in with the other scopes commonly used.

Figure 2-2: Class and Member Function Scope
2.4.2.2 Members
Declared in the Body of a Class
All members declared in the body of a class have class scope. Each identifier declared in the body of a class is within the scope of that class. All identifiers in the scope of a class can be freely used within the body of that class. In fact, C++ ensures that member functions can access any of the data members or other member functions even if they are first declared later in that class.
The following example demonstrates that members declared in the body of a class are accessible by all member functions within this class' scope.
class class_name {
public:
void member_function() {
cout <<data_member; //direct access to class members
}
private:
int data_member;
};
One member's declaration cannot be based on the existence of another member of the same class. The declarations of members should be able to be placed in any order with exactly the same meaning.
2.4.2.3 Members
Accessed from Outside of a Class' Body
Only those members declared as public can be used by clients outside of the body of a class. Such members can only be used by associating them with an object of their class. We can do this by using one of the member access operators (. or ->). These operators are used to select class members for a particular object, either directly through an object or indirectly through a pointer:
//directly through an object
object.datamember or object.mem_function()
//indirectly through a pointer
object_pointer->datamember
object_pointer->mem_function()
2.4.2.4 Members Defined
Outside of a Class but within Class Scope
It is common practice to place the implementation of member functions outside of a class. When a member function is defined outside of a class, we must differentiate that member from a non-member function. We do this by specifying the class in which the member resides. We must precede the member function's identifier with the class name and the scope resolution operator (::). Since the member function now has access to class scope, it has direct access to the other class members without any special qualification. This allows member functions to act the same as if they were implemented inside the body of the class itself (except that they are not automatically inline). For example, the following is a member function defined outside of a class:
class class_name {
public:
void member_function();
private:
int data_member;
};
void class_name::member_function() {
cout <<data_member; //direct access to class members
}
2.4.3 Member Access Control
Member Access Control: Accessibility of the members of a class.
The heart of data hiding is member access control. Member access control refers to the accessibility of the members of a class. When a class is defined, member access control determines who has access to class members. Placing members in public, protected, or private sections controls this accessibility. The following sections discuss declaring members in each of these contexts.
2.4.3.1 The Class
Interface's Public Section
Members declared within the public section include the data and operations that are accessible by all of the software that uses objects of that class (i.e., all of the clients). The public section specifies the functionality available to the client. This means that the information in the public section is transparent. All of the data members and member functions in the public section are accessible by both the client and member functions of the class. Once a client has created an object of a particular class, the client can directly access that object's public data and invoke the public member functions, using one of the member access operators (. or ->).
2.4.3.2 The Class
Interface's Protected Section
Members declared within the protected section include the data and operations that are accessible only by that class' members and derived classes. Clients cannot access the data members and member functions specified in the protected section.
2.4.3.3 The Class
Interface's Private Section
Members declared within the private section include the data and operations that are accessible only by that class' members. This means that the information in the private section is opaque. All of the data members and member functions in this section are inaccessible outside of the class. Clients cannot access the data members and member functions specified in the private section.
The private section is the default section for a class if none is specified. Members declared outside of the public, protected, or private sections are private. This means that if the keyword public or protected is not listed immediately after the class' header, subsequent members are private, not public!
Member functions defined as part of a class can access any of that class' private data. In fact, we should assume at this point that member functions are the only way to work with private data. Think of a class' member functions and private data as being able to work together as a team. No other parts of a program can interfere or alter the value of such private data. Therefore, think of our public member functions as being the interface between private data and the rest of the program. To gain access to private data, we can force clients to invoke the public member functions of that class. This provides access to data in a controlled way.
One advantage of private data is that class implementors are free to modify the representation of private members without affecting the client program. Think of private members as the implementation details, recording state information and performing utility operations.
2.4.4 Hints about Designing a Class Interface
Whether or not all three sections are defined for a particular class depends on that class' design. We only need to include those sections that make sense for solving the problem at hand. Many times we will exclude at least one of these sections (for now, the protected section).
Classes are very similar to structures. In fact, the syntax of a class definition is identical to that of a structure except for the default conditions and the keyword: class instead of struct. With structures, all members are available to clients by default (i.e., public). With classes, all members are restricted by default (i.e., private). Except for their default conditions, structures and classes are the same in C++.
Most, if not all, classes that we write will have a public section where at least some of the operations are declared. If no public section is listed in a class' interface, then clients have no direct access to any of the data and they are unable to use any of the operations.
Remember, the public section specifies a class' client interface. This determines what a client can see and do. To achieve data hiding, we must also include the private section and have it contain all of the data. This restricts access to data through member functions. By restricting the client's access to only public members, we can encapsulate implementation details within the class and hide them from the client. By taking advantage of this, the implementation of the class' member functions can be modified at a future time without causing the client programs to be altered. This is especially useful when the layout of the data members or size of the object changes as the class evolves. Of course, whenever the class interface is modified, the client will at least be required to recompile their code, even if the changes are limited to just the private section. By restricting the private data to a pointer to the actual data, we can later change the data representation without requiring that clients recompile their programs. The cost is one level of indirection to access the data. The benefits are in support of shared libraries and to minimize the impact of changes on client applications.
Practical Rule: Classes should be designed to be general purpose. Try not to lock a class into only one application. Focus on writing reusable code.
2.4.5 An Example of a Class Interface
Consider a simple example of a class implementing a stopwatch. At a minimum, a stopwatch requires member functions to start and stop the stopwatch and to obtain the current elapsed time. A stopwatch also requires data members that record the start and stop times and record whether or not the stopwatch is running.
Once we define a stopwatch class, the client can use as many different stopwatches as needed. Each stopwatch is an object and each is constructed from the same class and has the same member functions, but unique copies of the data members. This means that each stopwatch object has its own data members that contain values pertaining to that particular stopwatch object (e.g., one may be running with its start time recorded while another may have been stopped with both its start and stop times recorded).
As we design the stopwatch class, we need to consider whether public or private access is desired. Remember, public information is available to all clients of the stopwatch class. Therefore, the member functions to start and stop the stopwatch and to get the elapsed time need to be public so that clients can control the stopwatch and obtain the elapsed time. However, we do not want clients to have direct access to the data members containing the start and stop times in order to guarantee the integrity of that data. Clients should be allowed to access that information only in a controlled way through public member functions. This means that all of the data members in our stopwatch class should be private.
With this approach, the elapsed time for a stopwatch object is accessible only through member functions declared in the public section of the class interface. Such member functions are able to read the start and stop times and calculate the elapsed time. This provides a controlled way of accessing the private data members. The following represents the interface to our stopwatch class:
#include <ctime> //for clock_t type and functions
using namespace std;
class stopwatch {
public:
void start(); //start stopwatch
void stop(); //stop stopwatch
float get_time(); //get elapsed time
private:
bool is_running; //true if stopwatch running
clock_t start_time; //stopwatch start time
clock_t stop_time; //stopwatch stop time
};
The class simply declares the data type for objects that can later be defined. It isn't until an object is defined that the memory to hold the data members is allocated and can be initialized. This means that it is illegal to try to initialize a data member directly in the class interface, as shown below.
class stopwatch {
...
private:
bool is_running = false; //ILLEGAL
clock_t start_time = 0; //ILLEGAL
clock_t stop_time =0; //ILLEGAL
};
Practical Rule: Memory is not allocated for the class interface; rather, memory is allocated on an object by object basis.
2.4.6 A Complete Stopwatch Class
To complete the implementation of the stopwatch class we must implement the member functions that have been declared. For such a simple class, we have placed the implementation of the member functions inside the class interface (as shown in the following example). Whenever the implementation of a member function occurs in the class interface, C++ treats that member function as inline code and eliminates function calls. This can increase execution speed at the possible cost of additional object code. The down side is that other programmers using this class can see the inner-workings (i.e., the implementation) of the code. Although they should not be able to modify the implementation, those details are not hidden. Placing the member function definitions within the class also makes the class harder to read and is therefore generally avoided.
The following represents the implementation of our stopwatch class:
//stopwatch.h interface
and implementation (Ex0901)
#include <ctime> //for clock_t type and functions
using namespace std;
class stopwatch {
public:
void start() { //start stopwatch
is_running = true; //set state to running
start_time = clock(); //save starting time
}
void stop() { //stop stopwatch
if (is_running) //if running then
stop_time = clock(); //save ending time
is_running = false; //set state to stopped
}
float get_time() { //get elapsed time
if (is_running) //if running then
stop_time = clock(); //save ending time
float t = stop_time - start_time; //elapsed time
return (t / CLOCKS_PER_SEC); //return time in seconds
}
private:
bool is_running; //true if stopwatch running
clock_t start_time; //stopwatch start time
clock_t stop_time; //stopwatch stop time
};
This example demonstrates that each member function
can reference data members even though they may be declared later in the class.
The compiler treats all data members as though they are declared before any of
the member functions, regardless of the order they are listed in the class
interface.
2.5 The Class Implementation
Class Implementation: Implements the operations for a user defined type.
For all classes, even for small classes, we recommend separating the class interface from its implementation: one file for the interface and another for the implementation of the member functions. This allows a class' interface to be distributed to others without giving them access to the actual source code (of our member functions). The class interface should be placed in a header file. It should include the declaration of the member functions (i.e., function prototypes) but not their definition (unless we are including inline member functions). The file containing the class implementation can be compiled separately creating an object module that can be linked with the client programs or even included in a class library for future use.
The following sections describe how we can separate a class' interface from its implementation. We begin by examining the objectives of separating the class implementation and follow this with an explanation of the process.
2.5.1 Objectives of Separating the Class Implementation
Think of the class interface as the interface between the implementation of the class and the application using it. The interface must supply all of the information that is needed to use the class implementation. The class implementation is where we define the member functions. In this file we must include the class interface header file.
By separating the class interface from the implementation of its member functions, we can implement the member functions outside the body of the class interface, in the same file or in a different file. Separating these requires that we declare the member functions in the class interface by placing the function prototypes in the public, protected, or private sections. When we do this, the code generated for our member functions is no longer automatically treated as inline code. If our member functions are placed in the same file, we can specify that they be treated as inline code by using the inline keyword in their definitions. However, if they are placed in a separate file, hiding the implementation from other software designers and users, we are restricted from specifying our functions as inline unless we include that file as part of our interface file. If that is done, then all member functions must be declared to be inline. This is recommended only for special circumstances and should not be common practice.
2.5.2 Separating the Class Implementation from the Class Interface
To specify that a function defined outside of the class interface is a member of a class, the function's declaration must appear in the class interface. Then, we must place the class name followed by the scope resolution operator (::) before the function name in the function's definition. By qualifying a member function, we are informing the compiler that the subsequent function is a member of the named class. The compiler allows that function access to private data and member functions that are otherwise restricted. The compiler examines the class interface to confirm that the function mentioned has actually been declared as a member of the class. This prevents unauthorized access to the private data and member functions. If there isn't a match, the compiler generates an error. To perform such a check, the compiler requires that the class' interface be included in the file where the member functions are implemented.
It is important to specify the class to which a function belongs because two or more classes may use the same name for member functions. For example, there might be a stopwatch class, a counter class, and an alarm_clock class. Each may have member functions to start, stop, and access information. When we move the definition of the member functions outside of the class' interface, the class name followed by the scope resolution operator enables the compiler to match the function with the corresponding class. Plus, it lets the compiler know which functions are member functions versus non-member functions. This must be done whenever member functions are defined outside of the class interface, either within the same file or in different files.
If the scope resolution operator is not used to qualify a member function, the compiler considers the function to be one whose scope is global. Such a function does not represent a member function, regardless of the file in which it is defined.
When qualifying a member function, make sure to place the return type before the class name and the scope resolution operator. For example, for the start member function of class stopwatch, the member function's header would resemble the following:
void stopwatch::start() {
...
}
For a function that returns a pointer, this implies that the pointer declaration (asterisk) is to be written following the type and preceding the class name and scope resolution operator. It would not be correct to place the asterisk in between the :: and the function name! For example, the add member function of class tree returns a pointer to a node:
node* tree::add(node* node_ptr, node* new_node) {
...
}
Keep in mind that we only need to qualify a member function when its class interface is separate from its implementation and then only the function name itself must be qualified. All of the data members used within the implementation of a member function do not need to be qualified, and all other member functions called from within a member function do not need to be qualified. This is because member functions are within class scope and have direct access to all of the current object's members.
2.5.3 An Example of a Class Interface and Class Implementation
The following example demonstrates the implementation of class stopwatch, using one file for the class interface and another for the class implementation.
//stopwatch.h interface
(Ex0902)
#include <ctime> //for clock_t type and functions
using namespace std;
class stopwatch {
public:
void start(); //start stopwatch
void stop(); //stop stopwatch
float get_time(); //get elapsed time
private:
bool is_running; //true if stopwatch running
clock_t start_time; //stopwatch start time
clock_t stop_time; //stopwatch stop time
};
//stopwatch.cpp
implementation (Ex0902)
#include "stopwatch.h"
void stopwatch::start() { //start stopwatch
is_running = true; //set state to running
start_time = clock(); //save starting time
}
void stopwatch::stop() { //stop stopwatch
if (is_running) //if running then
stop_time = clock(); //save ending time
is_running = false; //set state to stopped
}
float stopwatch::get_time() { //get elapsed time
if (is_running) //if running then
stop_time = clock(); //save ending time
float t = stop_time - start_time; //elapsed time
return (t / CLOCKS_PER_SEC); //time in seconds
}
In this example, member functions are implemented in a separate file from the class interface. To specify that each function is a member function, the name of the class (stopwatch) appears directly before the name of the member function (e.g., start) separated by the scope resolution operator. Notice that each member function can directly access private members without using either the scope resolution operator or the member access operators.
2.5.4 An Example of a Class Implementation
To allow for member functions to be treated as inline when the class implementation is separated from the class interface, the implementation file must also be included in the header file. This programming practice should only be used when inline functions are necessary for performance reasons. The following illustrates the changes necessary in the class interface and implementation to support inline functions.
//stopwatch.h interface
(Ex0903)
#include <ctime> //for clock_t type and functions
using namespace std;
class stopwatch {
public:
void start(); //start stopwatch
void stop(); //stop stopwatch
float get_time(); //get elapsed time
private:
bool is_running; //true if stopwatch running
clock_t start_time; //stopwatch start time
clock_t stop_time; //stopwatch stop time
};
#include "stopwatch.icpp" //include inline implementation
//stopwatch.icpp inline
implementation (Ex0903)
inline void stopwatch::start() { //start stopwatch
is_running = true; //set state to running
start_time = clock(); //save starting time
}
inline void stopwatch::stop() { //stop stopwatch
if (is_running) //if running then
stop_time = clock(); //save ending time
is_running = false; //set state to stopped
}
inline float stopwatch::get_time() { //get elapsed time
if (is_running) //if running then
stop_time = clock(); //save ending time
float t = stop_time - start_time; //elapsed time
return (t / CLOCKS_PER_SEC); //time in seconds
}
2.6 Using a Class
Once we have designed and implemented a class, clients can create and reference objects of that class. To do so, the client program must include the class interface. This allows the client to use a class in the same way that they use data types. Then, the client can access all public members through these objects, using the member access operators.
The following sections demonstrate the process of using a class by defining objects.
2.6.1 An Example Client Program
The stopwatch class can be used to time various segments of a running program. In the following client program, three distinct stopwatch objects are defined. One object records the time required to access every element of a three-dimensional array using array subscripting. The second object records the time required to access every element of the same three-dimensional array using pointers. The third object records the total time for both methods. These three objects are defined as follows:
stopwatch total_timer; //create object for total time
stopwatch index_timer; //create object for index time
stopwatch pointer_timer; //create object for pointer time
Once defined, these stopwatch objects can be used to record the time it takes to access all elements of the three-dimensional array, first using array subscripting and then using the pointer method. The differences in time depend on the particular machine and the sophistication of the compiler used. The register storage class has been used for the array subscripts and pointers. The extent to which this affects the times obtained is also highly dependent on the machine and compiler used.
Each of the stopwatch objects is started in the main program by calling the start member function for each object. Then, each is stopped by calling the stop member function. Since each object is started and stopped at different times, they will contain different start and stop times. The elapsed time for each object is obtained by invoking the get_time member function. The value returned by get_time is displayed using cout.
//main.cpp (Ex0903)
#include <iostream>
using namespace std;
#include "stopwatch.h"
const long d3=50, d2=100, d1=200;
int array[d3][d2][d1];
int main() {
stopwatch total_timer; //create object for total time
stopwatch index_timer; //create object for index time
stopwatch pointer_timer; //create object for pointer time
total_timer.start(); //call start member function
index_timer.start(); //call start member function
for (register int k=0; k<d3; ++k)
for (register int j=0; j<d2; ++j)
for (register int i=0; i<d1; ++i)
array[k][j][i] = 42;
index_timer.stop(); //call stop member function
register int (*p2)[d2][d1], (*p2_end)[d2][d1];
register int (*p1)[d1], (*p1_end)[d1];
register int *p0, *p0_end;
pointer_timer.start(); //call start member function
p2 = array;
p1 = *p2;
p0 = *p1;
p2_end = p2 + d3;
while (p2++ < p2_end) {
p1_end = p1 + d2;
while (p1++ < p1_end) {
p0_end = p0 + d1;
while (p0 < p0_end) {
*p0++ = 42;
}
}
}
pointer_timer.stop(); //call stop member function
total_timer.stop(); //call stop member function
cout.setf(ios_base::showpoint);
cout.setf(ios_base::fixed, ios_base::floatfield);
cout.setf(ios_base::right, ios_base::adjustfield);
cout.precision(2);
cout <<"subscripts: " <<index_timer.get_time() <<endl;
cout <<"pointers: " <<pointer_timer.get_time() <<endl;
cout <<"total time: " <<total_timer.get_time() <<endl;
return (0);
}
Sample output:
subscripts: 3.12
pointers: 0.35
total time: 3.47
Since clients are not allowed to access the private
members, it is illegal for the client to set index_timer.start_time
to zero. The only way a client can access a private member's value is through a
public member function.
2.7 Defining an Object
Now that we have become familiar with defining a class interface, defining operations that can be performed by that class, and creating objects of that class, we are now going to take a closer look at objects: what they are and when memory is allocated for an object or set of objects.
2.7.2 What are Objects?
An object can be an instance of a class. Once an object is created, it exists and can be used. Its scope is its current block if local or the current module if global, and its lifetime is from the time of its creation until the time of its destruction. The constructor is implicitly invoked at the beginning of an object's lifetime and the destructor is implicitly invoked at the end of that object's lifetime. Each object is created and can be initialized through the use of a constructor, and is destroyed through the use of a destructor.
2.7.2 When is Memory Allocated for Objects?
Memory for objects is allocated either statically or dynamically. When objects are defined as global or static local, memory for the data members is allocated statically by the compiler. When objects are defined as local, memory is allocated statically on the program stack at run time. In the previous section, the memory for the stopwatch objects was allocated statically. This is because the objects were defined as local within the main program.
Alternatively, memory for objects can be allocated dynamically at run time using the new operator creating dynamic objects. This requires that we use a pointer to an object. Using new causes memory to be allocated and the constructor to implicitly be invoked. This is shown in the following code which dynamically allocates memory for an object and uses the constructor to initialize the data members.
stopwatch *timer_ptr; //pointer to an object
timer_ptr = new stopwatch; //invokes default constructor
For objects that are dynamically allocated using new (such as one referenced by timer_ptr), we must use delete to release that memory. When delete is used, the destructor for the object is implicitly invoked.
delete timer_ptr; //invokes the destructor for stopwatch
Whenever we allocate memory using new, we must use delete to release that memory. When we allocate memory for an object without subsequently deallocating it and we reach the end of the pointer's lifetime, we will no longer be able to access that dynamic object. This results in a memory leak and we will no longer be able to free up the memory allocated for that object.
2.8 Constructors
Constructors: Implicitly
invoked when the lifetime of an object begins; typically they are used to
initialize data members.
Constructors are special member functions that
are implicitly invoked when the lifetime of an object begins (i.e., when an
object of a class type is defined). The following sections focus on the
practical uses of constructors, what their primary purpose is, how to implement
them, and how they are used.
2.8.1 Purpose of Constructors
The purpose of constructors is to provide for automatic initialization of our objects when they are created. They specify how an object's memory is to be initialized. They may also be used to allocate any dynamic memory needed by an object to begin with.
It is important to realize that constructors do not allocate the memory for data members. Memory for data members is automatically allocated when an object is defined. The purpose of the constructor is to initialize that memory after the object is defined. Very simply, think of a constructor as an automatic means for initializing an object when its lifetime begins.
2.8.2 Syntax of Constructors
Constructors are members that have the same name as the class itself and no return type. Constructors may have arguments that are used to assist in the initialization process. Constructors that have no arguments have the following form:
class class_name {
public:
class_name(); //constructor with no arguments
};
class_name::class_name() {
//constructor implementation
}
Just like member functions, constructors must be declared in the class interface and defined either in the class interface or outside the class. If constructors are defined outside of the class, they should be placed in the same file as the other member function definitions.
Constructors cannot return values. When declaring or defining constructors, do not place a return type in the constructor's prototype or definition. Constructors must not contain return statements that attempt to return a value.
2.8.3 Default Constructor
Default
Constructor: A constructor with no arguments or with arguments
that all have default values.
A constructor with no arguments or all default arguments is a default constructor. There may be only one default constructor defined per class. It is the constructor used if no arguments are supplied when a client defines objects of that class.
We recommend supplying a default constructor with every class, either with no arguments or with all default arguments. This allows clients to define objects as naturally as they define variables of fundamental data types.
2.8.4 Compiler Supplied Constructors
If a constructor is not defined for a class, the compiler supplies one for us. The constructor supplied by the compiler does not perform any initialization. Therefore, the default values of the data members depend on the context in which the object is defined. For global and static local objects, the data members are initialized automatically to their zero equivalent value. For all others, the data members are not initialized.
Think of the ramifications for a moment. When data members are not initialized to begin with, clients are required to use member functions to explicitly set the values of private data members. Not only is this an extra step for the client, but if the client happens to bypass this step the results may not be predictable for local or dynamic objects. Therefore, we should always define a constructor to initialize all data members that affect the state of an object.
If we do not supply a default constructor and we have specified other constructors, C++ does not automatically supply a default constructor for us. If a default constructor is not supplied, clients must specify the initial values for all objects created and no temporary objects are allowed (such as with call by value). Just think how unnatural this would be if we were required to specify the initial values for every variable that we define in C++ and be restricted from using pass by value.
Practical Rule: A default constructor is supplied automatically by the compiler only when no other constructors are defined.
2.8.5 Public or Private Constructors
Constructors are typically declared in the public section of the class interface. This is because constructors are implicitly invoked when clients define objects and must be accessible to clients wishing to create objects. Therefore, in most cases our constructors will be declared in the public section.
A constructor declared in the private section can be used for building objects within the class itself (i.e., within class scope). Private constructors may be used to initialize objects that are defined as local to a member function or objects that are allocated dynamically within a member function. Private constructors are not accessible to clients. If all constructors are private, clients are prevented from creating objects of that type. Typically, if a constructor is declared in the private section, other constructors are declared in the public section for client use (with unique argument lists).
2.8.6 Example of a Constructor
Unlike the stopwatch class previously demonstrated, most classes are defined with at least a default constructor. The stopwatch example has been modified to include a constructor that assigns the object's data members to zero, initializing each object.
//stopwatch.h interface
(Ex0904)
#include <ctime> //for clock_t type and functions
using namespace std;
class stopwatch {
public:
stopwatch(); //default constructor
void start(); //start stopwatch
void stop(); //stop stopwatch
float get_time(); //get elapsed time
private:
bool is_running; //true if stopwatch running
clock_t start_time; //stopwatch start time
clock_t stop_time; //stopwatch stop time
};
//stopwatch.cpp
implementation (Ex0904)
#include "stopwatch.h"
stopwatch::stopwatch() { //default constructor
is_running = false;
start_time = 0;
stop_time = 0;
}
void stopwatch::start() { //start stopwatch
is_running = true; //set state to running
start_time = clock(); //save starting time
}
void stopwatch::stop() { //stop stopwatch
if (is_running) //if running then
stop_time = clock(); //save ending time
is_running = false; //set state to stopped
}
float stopwatch::get_time() { //get elapsed time
if (is_running) //if running then
stop_time = clock(); //save ending time
float t = stop_time - start_time; //elapsed time
return (t / CLOCKS_PER_SEC); //time in seconds
}
The following statements instruct the compiler to set aside memory for three objects (i.e., three instances of the stopwatch class). Since a constructor was defined as a member of the class, not only will memory be allocated for these objects, but the constructor will be implicitly invoked to initialize the data members for each object. This guarantees that if the member function get_time is called before member function start, the result is zero instead of garbage.
stopwatch total_timer; //create object for total time
stopwatch index_timer; //create object for index time
stopwatch pointer_timer; //create object for pointer time
2.9 Constructor Overloading
Classes may be defined with more than one constructor. By overloading constructors, we allow clients flexibility in how they initialize objects. Through function overloading, multiple constructors can be defined each with a different number or type of arguments. The compiler determines which constructor is used based on the number and type of arguments supplied when an object is defined. These arguments allow objects to be initialized to different states and enable constructors to be overloaded, distinguishing one from another.
The following sections discuss how arguments can be used to overload constructors.
2.9.1 Constructor Arguments
Even though constructors cannot return values, they can have arguments. The argument list for the constructor's prototype and function definition have the same format as a standard C++ function. Arguments allow clients to specify the initial values of data members, where different objects can be specified to start off with different initial values.
The following example demonstrates the definition of a constructor where data members are assigned values based on the arguments supplied by the client.
//salary.h interface
(Ex0905)
class salary {
public:
salary(float time, float wage, float tax) {
total = time * wage;
fed = tax;
fica = .0765;
pay = total - total * fica;
if (total * 52 > 6400)
pay = pay - (total - 6400/52) * fed;
}
private:
float pay; //pay after federal and fica taxes
float total; //total income before taxes
float fed; //federal tax rate
float fica; //social security tax rate
};
If only one constructor is defined and if it requires arguments, the client must specify actual arguments for every object of that type. The following example defines two objects of type salary:
salary jones(40, 5.15, .15);
salary smith(20, 5.15, .15);
When objects are dynamically allocated, we can also supply initial values through argument lists:
salary* ptr; //pointer to object of type salary
ptr = new salary(35, 2.5, .28); //3 constructor arguments
...
delete ptr; //invokes salary destructor
2.9.2 Constructors with Default Arguments
Default arguments are ideal in circumstances where most objects are expected to have the same initial values. With a default argument list, clients have a choice whether or not to specify an object's initial values. This allows the default values to be overridden without forcing clients to always supply initial values when defining their objects. The best of both worlds!
The following class demonstrates the definition of an inline constructor that has default arguments. If the implementation of the constructor is moved to another file, the defaults must still be specified in the constructor's declaration in the class interface.
//salary.h interface
(Ex0906)
class salary {
public:
salary(float time=40, float wage=5.15, float tax=.15) {
total = time * wage;
fed = tax;
fica = .0765;
pay = total - total * fica;
if (total * 52 > 6400)
pay = pay - (total - 6400/52) * fed;
}
private:
float pay; //pay after federal and fica taxes
float total; //total income before taxes
float fed; //federal tax rate
float fica; //social security tax rate
};
To demonstrate the use of default arguments, examine the following client program. The client defines three objects: one using all of the default values, another using two of the default values, and the last overriding all of the default values. To override the default values, the client can specify the first argument, the first two arguments, or all three arguments since each formal argument is defined to have a default value. If the first formal argument was not defined to have a default value, the client would be required to always specify that value when defining objects.
salary jones; //uses all three default args
salary smith(20); //uses last two default args
salary brown(35, 2.5, .28); //uses no default args
Take a close look at the client's program. To use the default, nothing is placed after the object's name. In this case, the object's definition resembles a variable's definition. When we want to use all default values, parentheses should not be used. If they are used, the compiler considers the statement to be a function prototype for a function that takes no arguments and returns an object of class salary.
//function prototype...not an object definition!
salary jones(); //function taking no args returning salary
It is illegal to supply more than one default constructor. Providing the following constructors in the class' definition is ambiguous and is not allowed by the compiler.
class salary {
public:
salary(float time=40, float wage=5.15, float tax=.15);
salary(); //ILLEGAL-duplicate default constructor
...
};
2.9.3 Multiple Constructors
More than one constructor may be supplied with each class using constructor overloading. Constructors are overloaded in the same way that functions are overloaded. With constructor overloading, each constructor must have a unique formal argument list. The function's signature (the number and type of arguments) is used to determine the correct constructor to use.
No two constructors can have the same formal argument list or be the default constructor. Each overloaded constructor must have a unique signature, without lvalue transformations, promotions, or type conversions resulting in ambiguities. This is because it must be clear which constructor is to be used based on the actual arguments supplied. C++ matches the argument list when an object is created with the signature of the constructor declarations to determine which overloaded constructor to use. If more than one constructor has the same formal argument list, C++ will not be able to determine which to use and an error will be generated.
Practical Rule: If more than one constructor is supplied as part of a class definition, C++ executes the one whose arguments most closely match those in the class definition according to function overloading rules.
2.9.4 Example of Constructor Overloading
The salary class has been modified to demonstrate constructor overloading. The constructor is overloaded with several constructors that differ in their formal argument lists. Each constructor calls a common private member function to compute the take home pay.
//salary.h interface
(Ex0907)
class salary {
public:
//default constructor taking no arguments
salary() {
total = 40*5.15; //default time and wage
fed = .15; //default tax
fica = .0765; //fixed fica tax
compute_pay();
}
//constructor taking one or two arguments
salary(float time, float wage=5.15) {
total = time*wage; //compute time and wage
fed = .15; //default tax
fica = .0765; //fixed fica tax
compute_pay();
}
//constructor taking three arguments
salary(float time, float wage, float tax) {
total = time*wage; //compute time and wage
fed = tax; //set tax rate
fica = .0765; //fixed fica tax
compute_pay();
}
private:
void compute_pay() {
pay = total - total * fica;
if (total * 52 > 6400)
pay = pay - (total - 6400/52) * fed;
}
float pay; //pay after federal and fica taxes
float total; //total income before taxes
float fed; //federal tax rate
float fica; //social security tax rate
};
The following object definitions cause the constructor that most closely matches the supplied argument list to be invoked following the rules of function overloading.
salary jones; //default constructor used
salary smith(20); //2 arg constructor (default 2nd arg)
salary brown(35, 2.5, .28); //3 arg constructor
This also applies to dynamically allocated objects. We can supply argument lists to allow different constructors to be used depending on the circumstances.
salary *ptr; //pointer to object of type salary
ptr = new salary; //invokes default constructor
ptr = new salary(20); //invokes 2 arg constructor
ptr = new salary(35, 2.5, .28); //invokes 3 arg constructor
...
delete ptr; //invokes salary destructor
2.10 Initialization Lists
Initialization
List: Supplies the initial values for data members.
There are two ways to initialize data members using a constructor. We can assign values to them in the body of the constructor as we have already seen, or we can specify an initialization list. The initialization list differentiates between the process of initialization and assignment. It is the only way to perform true initialization.
The following sections discuss the difference between initialization and assignment, introduce the syntax of initialization lists, and demonstrate how initialization can be used in our programs.
2.10.1 Initialization Lists versus Assignment
By definition, initialization occurs during the construction of an object and gives the data members initial values. Assignment occurs when the body of the constructor is executed and gives an existing data member a new value, overwriting the default value it was given during construction. Notice the timing; initialization occurs before the body of the constructor is executed. Therefore, initialization occurs before assignment.
To initialize data members during construction, we can specify the initial values of our data members through initialization lists. Initialization lists are the only mechanism available for initializing constant data members or reference data members; such initialization cannot be accomplished from within the body of the constructor because assignment to constants and references is illegal. Initialization lists are also more efficient when the data members are objects of other classes because we no longer need to both initialize and then assign values to an object's members.
2.10.2 Syntax of Initialization Lists
An initialization list is specified at the end of
the constructor's argument list. It is separated from the argument list by a
colon followed by a comma separated list of data member identifiers with their
initial value(s) in parentheses. The initial value(s) can be literals,
constants, or expressions. The order in which the initialization takes place is
the order of the declaration of the data members within the class, not
the order of the initializers in the initialization list. The initialization
list is always associated with the constructor definition (rather than its
declaration).
Initialization lists have the following form:
class_name {
public:
//a constructor definition with an initialization list
class_name(arg_list) :
data_member_1(initial_value),
data_member_2(initial_value) {
//constructor implementation
}
...
};
or,
class_name {
public:
//a constructor declaration with an initialization list
class_name(arg_list); //no initialization list here
...
};
//initialization list must be with constructor definition
class_name::class_name(arg_list) :
data_member_1(initial_value),
data_member_2(initial_value) {
//constructor implementation
}
2.9.4 Example of Initialization Lists
The following example demonstrates the use of initialization lists. Since total, fed, and fica are specified as constant data members they must be initialized using initialization lists. Simple expressions are used to initialize two of the data members: total and fed. The data member pay is initialized in the body of the constructor since its initial value cannot be easily represented as a simple expression.
//salary.h interface
(Ex0908)
class salary {
public:
salary(float time=40, float wage=5.15, float tax=.15) :
total(time * wage),
fed(tax),
fica(.0765) {
pay = total - total * fica;
if (total * 52 > 6400)
pay = pay - (total - 6400/52) * fed;
}
private:
float pay; //pay after federal and fica taxes
float total; //total income before taxes
const float fed; //federal tax rate
const float fica; //social security tax rate
};
2.9.5 Incorrectly using Initialization Lists
By revising this class interface, we can also initialize pay using a simple expression in the initialization list. By initializing pay to be the total wage minus the amount due to fica taxes, pay will not have the correct value. This is because initialization is in the order of the declaration of the data members and not in the order of the initializers in the initialization list. When we attempt to initialize pay, we find that neither fed or fica have been initialized, resulting in garbage. This is easily corrected by moving the declaration of pay to the end of the list of data members.
//salary.h interface
(Ex0909)
class salary {
public:
salary(float time=40, float wage=5.15, float tax=.15) :
total(time * wage),
fed(tax),
fica(.0765),
pay(total - total * fica) {
if (total * 52 > 6400)
pay = pay - (total - 6400/52) * fed;
}
private:
float pay; //ERROR-should be last member
float total; //total income before taxes
const float fed; //federal tax rate
const float fica; //social security tax rate
2.11 Destructors
Destructors: Implicitly
invoked when the lifetime of an object ends;
typically they are used to release any resources set aside for the
object.
Destructors are special functions that are implicitly invoked when the lifetime of an object is over. The primary purpose of a destructor is to deallocate any dynamically allocated memory and to release any other resources (e.g., open files) allocated for the object whose lifetime is over. Destructors are only bypassed when exit is called or the program aborts.
2.11.1 Destructors and Exception Handling
Destructors are also implicitly invoked during exception handling when control is searching backward for the appropriate catch block. This is called unwinding the stack. If an exception occurs while constructing an object, the destructor is automatically invoked to correctly destroy a partially constructed object. Ensuring that destructors are properly called during exception processing was one of the primary reasons that exception handling was introduced into the C++ language.
2.11.2 Syntax of Destructors
Like constructors, destructors also have no return type and have the same name as their class except that the name is preceded with a tilda (~). They are different than constructors in that they cannot have any arguments. Because there are no arguments to distinguish one function from another, the destructor cannot be overloaded.
class class_name {
public:
~class_name(); //destructor
...
};
class_name::~class_name() {
//destructor implementation
}
10.3.2 The Copy Constructor and Assignment Operator
Copy Constructor Makes a copy of an object
upon an object's creation.
Assignment Operator: Makes a copy of an object when one
object is assigned to another.
Up until now, we have focused on the birth and death of an object. That is, we have focused our attention on creating and destroying objects. There are other contexts in which operations on objects occur. For example, we can initialize an object to the value of another object upon creation. Or, we can pass an object to a function by value, causing a temporary object to be created. We can also assign one object to another. In any of these cases, member functions must be supplied to perform such operations. These are the copy constructor and the assignment operator.
The copy constructor is implicitly invoked when we create a new object and give it an initial value, such as when we initialize an object to the value of another object, when we pass an object by value, creating a temporary, or when we return an object by value. The assignment operator is used when we give an existing object a new value.
The following example demonstrates some common uses of the copy constructor and the assignment operator.
//main.cpp (Ex1004)
#include "name.h"
int main() {
name smith("Sue Smith"); //constructor with arg used
name clone1="Sue Smith"; //constructor with arg used
name clone2(smith); //copy constructor used
name clone3=smith; //copy constructor used
clone1 = smith; //assignment operator used
return (0);
}
The first and second definitions
use the constructor and explicitly pass the initial value for the object's data
member. The third and fourth definitions use an existing object to initialize a
new object. In these definitions, the copy constructor is used to perform this
initialization. In the last assignment statement, one object is assigned to
another. This is the only situation that uses the assignment operator.
2.12 Memberwise Copy
Memberwise Copy: The data members of one object are copied
into the data members of another object.
We can either explicitly define the copy constructor and assignment operator member functions as part of our class' definition or allow the compiler to automatically supply them. When automatically supplied, only the members of the objects are copied; this is called memberwise copy. With memberwise copy, these functions simply copy the contents of each of the object's data members but none of the dynamic memory that those data members point to.
Memberwise copy does not usually produce the desired results when an object's data members are pointers. Therefore, classes that allocate and deallocate memory dynamically must supply their own copy constructor and assignment operator member functions. When we write our own, we are replacing the default member functions generated by the compiler.
The next section demonstrates how we can implement the copy constructor. The assignment operator is similar and is discussed with operator overloading next lecture. For now, think of the copy constructor and the assignment operator as performing similar functions, just under different circumstances. Even though the operations performed by these two special member functions are similar, the context in which they are used is quite different.
Practical
Rule: Whenever we have a class that allocates and deallocates memory
dynamically, we should provide an explicit copy constructor, destructor, and
assignment operator.
2.13 Dynamic Memory Considerations
Pointers are useful as data members when dynamically allocated data structures, such as arrays, linked lists, or trees are used as part of our objects. When pointers are used as data members, the member functions become responsible for allocating and deallocating the dynamic memory. When we work with dynamic memory, we must design our classes with specialized member functions to take care of allocating, deallocating, and copying that memory. This includes providing a destructor, a copy constructor, and an assignment operator as part of the class; we should not rely on the automatically generated member functions.
The following sections discuss the impact on the design of our constructor(s), destructor, and copy constructor when allocating dynamic memory inside of a class.
2.13.1 Constructors Allocating Dynamic Memory
The main purpose of a constructor is to initialize an object's data members when that object is created. In addition, constructors should be responsible for allocating and initializing any dynamic memory required upon the creation of an object. For example, constructors can be used to allocate memory for arrays or header nodes of linked lists at run time.
With arrays, constructors can be used to allocate the correct amount of memory at run time. This means that we can wait until run time to determine how much memory to allocate and we are guaranteed that the memory is allocated before the client invokes any of the other member functions. This is because constructors are implicitly invoked upon an object's creation. Therefore, the constructor is the ideal place to perform such operations.
2.13.2 Example of Constructors Allocating Dynamic Memory
The next example modifies the class presented in Section 10.3 and demonstrates the use of a dynamically allocated array. Instead of using an array of characters we now use a pointer to a character as our data member and include the length of the array (making sure to leave room for the terminating nul character). This allows the class to dynamically allocate exactly enough memory to hold the string instead of specifying a constant size at compile time.
//name.h interface
(Ex1005)
class name {
public:
name(char* = ""); //default constructor
name(const name &); //copy constructor
~name(); //destructor
name &operator=(name &); //assignment op (Lecture 9)
private:
char* ptr; //pointer to name
int length; //length of name including nul char
};
//name.cpp
implementation (Ex1005)
#include <cstring>
using namespace std;
#include "name.h"
name::name(char* name_ptr) { //constructor
length = strlen(name_ptr)+1; //get name length + nul char
ptr = new char[length]; //dynamically allocate space
strcpy(ptr, name_ptr); //copy name into new space
}
//main.cpp (Ex1005)
#include "name.h"
int main() {
name blank; //default constructor used
name* ptr_name; //pointer to object of type name
ptr_name = new name; //default constructor
name smith("Sue Smith"); //one arg constructor used
return (0);
}
When objects of class name are created, the only memory allocated before the constructor is invoked is enough memory to hold two data members: one for a pointer (ptr) and another for the array size (length). Once the constructor is invoked, the array's memory is dynamically allocated and then subsequently initialized. The use of a default argument allows clients the option of specifying a initial string as part of an object's definition.
When object blank is created, the default constructor is invoked. Since no argument is supplied, the constructor dynamically allocates memory for only one character and initializes it to the nul character. This also occurs when the constructor is invoked for ptr_name. A dynamic object is created when we say ptr_name = new name; the use of new implicitly causes the default constructor to be invoked for this object.
When object smith
is created, the same constructor is invoked, but this time the initial value is
passed as an argument. In this case, the constructor dynamically allocates
enough memory to hold the entire character string supplied in addition to the
terminating nul character. Then, the
string passed in is copied into this memory.
We can also override the constructor's default argument when we dynamically allocate objects themselves. With ptr_smith, we override the constructor's default argument by specifying the initial value as part of the object's definition:
name* ptr_smith; //pointer to object of type name
ptr_smith = new name("Sue Smith"); //one arg constructor
2.13.3 Destructors Deallocating Dynamic Memory
When the lifetime of an object is over, the destructor for the object is automatically invoked. The primary use of the destructor is to deallocate any memory that was dynamically allocated for the object and release any other resources (e.g., close open files). For practical purposes, if no dynamic memory is allocated as part of a class, there may be no need for a destructor to be supplied.
If a destructor is not supplied or does not do anything, then when the lifetime of the object is over the compiler will invoke destructors for any of its data members and then release memory for those members.
So, what happens if we don't implement a destructor? The program will run just fine. But, if we create too many different objects and run out of memory, there will be no way to free up that memory to be reallocated while the program is running. Therefore, make a rule that whenever memory is allocated dynamically from anywhere within a class' member functions (including the constructor) that a destructor must be written to deallocate that same memory. If a destructor is not supplied, the memory for the data members is deallocated at the end of the object's lifetime, but any associated dynamic memory allocated for that object is left hanging without a means of being accessed. The result is a memory leak.
The following is the implementation of a destructor that releases the dynamically allocated memory for the name class. Providing this destructor guarantees than any dynamic memory allocated as part of an object will be released when that object's lifetime is over.
//name.cpp
implementation (Ex1005)
#include <cstring>
using namespace std;
#include "name.h"
name::~name() { //destructor
delete[] ptr; //deallocate space
}
2.13.4 The Copy Constructor and Dynamic Memory
Shallow Copy: The data members of one object are copied
into the data members of another object without taking any dynamic memory
pointed to by those data members into consideration.
Deep Copy: Any dynamic memory pointed to by the data members is
duplicated and the contents of that memory is copied.
In every class, the compiler automatically supplies both a copy constructor and an assignment operator if we don't explicitly provide them. Both of these member functions perform copy operations by performing a memberwise copy from one object to another. In situations where pointers are not members of a class, memberwise copy is an adequate operation for copying objects. However, it is not adequate when data members point to memory dynamically allocated within the class. First, let's understand why this is so. Then, we will examine a solution by defining our own copy constructor.
2.13.4.1 Problems with
Memberwise Copy
Memberwise copy takes each data member and copies it into the destination object's data member. When memberwise copy is used with objects that point to dynamic memory, memberwise copy causes the address (i.e., the value of the pointers) to be copied and not the contents to which the pointers refer. Therefore, each time a copy operation is performed, both objects (the source and destination) end up pointing to the same memory.
The following demonstrates the problem using the previous class without a copy constructor; do not write classes this way! This is an example of how not to work with dynamic memory.
name smith("Sue Smith"); //one arg constructor used
name clone(smith); //default copy constructor used
Figure 2-3 illustrates the problem. When smith is created, the constructor is called and initializes the object by dynamically allocating enough memory to hold the string "Sue Smith". At this point all is fine. When the object clone is created, the initial value is the object smith. This is a special case of initialization where we use the values of one object to initialize the other. Since we have not defined a copy constructor, the compiler generates one for us which performs a memberwise copy: copying the contents of ptr (i.e., an address) and length from object smith to object clone. We now have two objects pointing to the same region of dynamic memory. This is often referred to as a shallow copy.

Figure 2-3: Incorrect Use of the Copy Constructor
Consider what happens when the destructor for smith is called. The dynamic memory is deallocated, but object clone is still pointing to it! The destructor for clone will attempt to deallocate the same memory a second time: a serious error. To fix the problem, we must implement our own copy constructor that takes into account the fact that we have dynamically allocated memory as part of our objects.
2.13.4.2 The Solution
by Implementing a Copy Constructor
The problems created by memberwise copies can be solved by overloading the copy constructor. This member function is simply another constructor with a special form. The copy constructor takes an object as an argument and uses that object to initialize the current object.
class_name(const class_name &class_object);
Within the body of this constructor we must write the code to duplicate all dynamic memory that has been allocated for the source object. We can do this by allocating the same amount of memory that the class_object has allocated for it and copying the contents into the current object. This is often referred to as a deep copy.
The following implements the correct copy constructor for class name.
//name.cpp
implementation (Ex1005)
#include <cstring>
using namespace std;
#include "name.h"
name::name(const name &obj) { //copy constructor
length = obj.length; //get length
ptr = new char[length]; //dynamically allocate space
strcpy(ptr, obj.ptr); //copy name into new space
}
//main.cpp (Ex1005)
#include "name.h"
int main() {
name smith("Sue Smith"); //one arg constructor used
name clone(smith); //copy constructor used
return (0);
}
When we initialize object clone with the value of object smith, we allocate separate memory for this new object. Figure 2-4 illustrates the result using our copy constructor.

Figure 2-4: Correct Use of the Copy Constructor
Practical Rule: Whenever we have a class that dynamically allocates and deallocates memory and has data members that are pointers, a copy constructor must either be written or made private to prohibit copy operations. Otherwise, clients may experience unwanted side effects since the objects share common memory. Such side effects may range from objects changing unexpectedly (due to corresponding changes to objects that share this same memory) to corrupting the memory management system.
2.13.5 Passing Objects to and Returning Objects from Functions
C++ allows objects of a class to be used as a function's arguments and returned value. Objects can be passed by value or by reference. With pass by value, a copy of all of the data members is placed on the program stack by implicitly invoking the copy constructor. The function being called uses this local copy when referencing the object's data members. The same thing happens when we return an object by value from a function. Since a temporary object is created, the only thing the calling function can do is immediately use that object or assign it to another object. It's lifetime is over at the end of the statement containing the call.
The following example uses pass by value to pass an
object to function and to return an
object.
//name.h interface
(Ex1006)
class name {
public:
name(char* = ""); //default constructor
name(const name &); //copy constructor
~name(); //destructor
name &operator=(name &); //assignment op (Lecture 9)
const char* get_name(); //get pointer to name
private:
char* ptr; //pointer to name
int length; //length of name including nul char
};
//name.cpp
implementation (Ex1006)
#include <cstring>
using namespace std;
#include "name.h"
name::name(char* name_ptr) { //constructor
length = strlen(name_ptr)+1; //get name length + nul char
ptr = new char[length]; //dynamically allocate space
strcpy(ptr, name_ptr); //copy name into new space
}
name::name(const name &obj) { //copy constructor
length = obj.length; //get length
ptr = new char[length]; //dynamically allocate space
strcpy(ptr, obj.ptr); //copy name into new space
}
name::~name() { //destructor
delete[] ptr; //deallocate space
}
const char* name::get_name() { //get pointer to name
return(ptr);
}
//main.cpp (Ex1006)
#include <iostream>
using namespace std;
#include "name.h"
name function(name); //pass and return by value
int main() {
name smith("Sue Smith"); //constructor with arg used
//call function by value & display from object returned
cout <<function(smith).get_name() <<endl;
return (0);
}
name function(name obj) {
cout <<obj.get_name() <<endl;
return (obj);
}
Figure 2-5 shows the results of passing an object by value and returning an object by value. The copy constructor is implicitly invoked twice: once to pass the argument's value and a second time to return the value. If a copy constructor was not implemented, the temporary objects ptr data member would point to the same memory as smith. smith's dynamic memory would then be inappropriately deallocated at the end of the temporary's lifetime.

Figure 2-5: Copy Constructor used when Objects are Passed by Value
Alternatively, call by reference can be used and represents a more efficient means of using objects as arguments. Using call by reference, the original memory allocated for the object is directly accessed; a separate copy is not generated. This means that the copy constructor does not need to be invoked when passing an object by reference or when returning by reference (i.e., via a reference return type). Be careful not to return a reference to a local object.
We highly recommended using pass by reference and return by reference when passing objects of a class for efficiency reasons. It is an improvement over pass or return by value since it reduces the need to perform extra copy operations. In fact, the only time we recommend using pass by value (when passing objects of a class) is when a complete and duplicate copy is necessary. We also recommend declaring the arguments as const whenever objects are not to be modified by the function.
The following code is an example of using call by reference instead of call by value. By using the reference operator, we avoid the overhead of calling the copy constructor and destructor member functions twice. The results of calling and returning by reference are shown in Figure 2-6.
//main.cpp (Ex1007)
#include <iostream>
using namespace std;
#include "name.h"
name &function(name &); //pass and return by value
int main() {
name smith("Sue Smith"); //constructor with arg used
//call function by value & display from object returned
cout <<function(smith).get_name() <<endl;
return (0);
}
name &function(name &obj) {
cout <<obj.get_name() <<endl;
return (obj);
}

Figure 2-6: Pass by Reference and Return by Reference