CSCI 123 Introduction to Programming Concepts in C++ Brad Rippe Brad Rippe More Classes and Dynamic Arrays
Overview 11.4 Classes and Dynamic Arrays Constructors, Destructors, Copy Constructors
Separation #include <iostream> // includes // declaration class MyDataType { Declaration public: double getmydt() const; void setmydt(double dt); private: double dt; }; int main() { // some instructions } // implementation void MyDataType::getMyDT() const { return this->dt } Implementation double MyDataType::setMyDT(double dt) { this->dt = dt; }
Separation of the interface from the implementation C++ provide a mechanism for separating the declaration from the implementation Implementation is the actual instructors for how the class operates. It determines how the function prototypes and constructor prototypes do their work Declaration (header) provides the prototypes for the class constructors and functions. It also declares any members of the class. The declaration is placed in a file with an h file extension The implementation (cpp) is placed in a file with a cpp file extension Both files should have the same file name, the name of the class Example: Bike.h and Bike.cpp
#include headers Assume we have a Bike.cpp and a Bike.h How do we include these files? What needs to happen? The #include < > statement is a preprocessor directive What did this instruction do?
#include headers Bike.h and Bike.cpp You must include the include the Bike.h in your main file. This will be a separate file from the Bike.h and the Bike.cpp This allows your main to be aware of the Bike class. Without the include, your main function doesn t understand what a Bike is. Likewise your Bike.cpp must include the Bike.h file so that it understand what a bike is
More Preprocessor Directives The preprocessor handles preprocessing directives, which can define and undefine macros, establish regions of conditional compilation, include other source files, and control the compilation process somewhat. A marco is a name that represents other text, called the macro replacement text When the macro name is seen in the source file, the preprocessor replaces the name with the replacement text Example #define ONE_HUNDRED 100
Preprocessor Directives #ifdef defines a conditional compilation region (if defined identifier) #ifdef BIKE_H #ifndef defines a conditional compilation region (if not defined identifier) #ifndef BIKE_H #endif terminates a conditional compilation region (end if)
Preprocessor Directives We can use the directives to ensure that the header file gets included once #ifndef BIKE_H //if the symbol is not defined #define BIKE_H // continue processing class Bike { public: }; #endif // Bike Declaration
Classes and Dynamic Arrays A dynamic array can have a class as its base type Bike *bikes = new Bike[25]; // 25 can be a variable A class can have a member variable that is a dynamic array In this section you will see a class using a dynamic array as a member variable.
Program Example: BikeStore We will define the class BikeStore BikeStore objects use dynamic arrays whose size is determined when the program is running We will redefine the class Bike
The BikeStore Constructors The default BikeStore constructor creates an object with a maximum array of 10 Another BikeStore constructor takes an argument of type int which determines the maximum array length of the object A third BikeStore constructor takes an array of bikes as an argument and the size sets maximum size of the bikes array to the size parameter copies the bikes from the parameter to the object s inventory
The BikeStore Interface In addition to constructors, the BikeStore interface includes: Member functions int getsize() const; int getcapacity() const; int resize(); friend ostream& operator << (ostream& out, const BikeStore& astore); Copy Constructor discussed later Destructor discussed later
A BikeStore Sample Program Using the BikeStore interface, we can write a program using the BikeStore class The program creates a Bike Store Adds two bikes to the store Outputs using the insertion operator from the BikeStore class and the Bike class
The BikeStore Implementation BikeStore uses a dynamic array to store its inventory BikeStore constructors call new to create the dynamic array for member variable minventory If msize == mcapacity, we resize our inventory for more bikes by doubling current capacity If mcapacity >= msize*2, we shrink the inventory Capacity and size determine at runtime
The BikeStore Implementation BikeStore members operator << addbike() removebike() movebikes() resize() shrink()
Dynamic Variables Dynamic variables do not "go away" unless delete is called Even if a local pointer variable goes away at the end of a function, the dynamic variable it pointed to remains unless delete is called A user of the BikeStore class could not know that a dynamic array is a member of the class, so would not be expected to call delete when finished with a BikeStore object
Destructors A destructor is a member function that is called automatically when an object of the class goes out of scope The destructor contains code to delete all dynamic variables created by the object A class has only one destructor with no arguments The name of the destructor is distinguished from the default constructor by the tilde symbol ~ Example: ~BikeStore( );
~BikeStore The destructor in the BikeStore class must call delete [ ] to return the memory of any dynamic variables to the freestore Example: BikeStore::~BikeStore() { } delete [] minventory;
BikeStore Output Bike Store created... Bike store size is 0 Bike store capacity is 10 Adding bikes to the store Bike added successfully true Bike added successfully true Bike store size is 2 Bike store capacity is 10 Outputting Bike Store: Adding 10 bikes to the store Resize... Bike store size is 12 Bike store capacity is 20 Removing 10 bikes from the store Shrinking... Shrinking... Shrinking... Bike store size is 2 Bike store capacity is 2 Sample2 from destructors and constructors
Pointers as Call-by-Value Parameters Using pointers as call-by-value parameters yields results you might not expect Remember that parameters are local variables No change to the parameter should cause a change to the argument The value of the parameter is set to the value of the argument (an address is stored in a pointer variable) The argument and the parameter hold the same address If the parameter is used to change the value pointed to, this is the same value as the argument pointed to by the argument! BadPointerCopy.cpp
BadPointerCopy.cpp example What happened? BikeStore lastore; BikeStore ocstore; Bike truth("ellsworth Truth", 17, 26.0); Bike id("ellsworth Id", 19, 26.0); lastore.addbike(truth); lastore.addbike(id); ocstore = lastore; ocstore.removebike(truth); cout << "Orange County Store\n"; outputsizeandcapacity(ocstore); cout << ocstore; // Shrink() killed lastores pointer
Copy Constructors The problem with using call-by-value parameters with pointer variables is solved by the copy constructor. A copy constructor is a constructor with one parameter of the same type as the class The parameter is a call-by-reference parameter The parameter is usually a constant parameter The constructor creates a complete, independent copy of its argument
BikeStore Copy Constructor This code for the BikeStore copy constructor Creates a new dynamic array for a copy of the argument Making a new copy, protects the original from changes BikeStore::BikeStore(const BikeStore& abikestore) { } msize = abikestore.msize; mcapacity = abikestore.mcapacity; minventory = new Bike[mCapacity]; for(int i = 0; i < msize; i++) minventory[i] = abikestore.minventory[i]; CopyConstructor.cpp
Calling a Copy Constructor A copy constructor can be called as any other constructor when declaring an object The copy constructor is called automatically When a class object is defined and initialized by an object of the same class When a function returns a value of the class type When an argument of the class type is plugged in for a call-byvalue parameter
The Need For a Copy Constructor This code from BadPointerCopy.cpp ocstore = lastore; Causes the pointer in ocstore to point to the variables in lastore's inventory ocstore.minventory = lastore.minventory minventory is a pointer; we now have two pointers pointing at the same variable
The Need For a Copy Constructor (cont.) Since ocstore.minventory and lastore.minventory are pointers, they now point to the same dynamic array Ellsworth Truth Ellsworth Id ocstore.minventory lastore.minventory
The Need For a Copy Constructor (cont.) When ocstore resizes the inventory, it deletes the original array. This returns the dynamic array pointed to by lastore to the freestore Undefined ocstore.minventory lastore.minventory lastore.minventory now points to memory that has been given back to the freestore! While ocstore is reassigned
The Need For a Copy Constructor (cont.) Two problems now exist for object lastore Attempting to output lastore.minventory is likely to produce an error When lastore goes out of scope, its destructor will be called Calling a destructor for the same location twice is likely to produce a system crashing error
Copy Constructor Demonstration Using the same example, but with a copy constructor defined ocstore.minventory and lastore.minventory point to different locations in memory Ellsworth Truth Ellsworth Id Ellsworth Truth Ellsworth Id ocstore.minventory lastore.minventory
Copy Constructor Demonstration (cont.) When lastore goes out of scope, the destructor is called, returning lastore.minventoy to the freestore Ellsworth Truth Ellsworth Id undefined ocstore.minventory lastore.minventory ocstore.minventory still exists and can be accessed or deleted without problems
When To Include a Copy Constructor When a class definition involves pointers and dynamically allocated memory using "new ; include a copy constructor Classes that do not involve pointers and dynamically allocated memory do not need copy constructors
The Big Three The copy constructor The assignment operator The destructor If you need to define one, you need to define all
The Assignment Operator Given these declarations: BikeStore lastore; BikeStore ocstore; the statement ocstore = lastore; is legal But, since BikeStore's member value is a pointer, we have ocstore.minventory and lastore.minventory pointing to the same memory location
Overloading = The solution is to overload the assignment operator = so it works for BikeStore Remember the default operator copies members operator = is overloaded as a member function Example: operator = declaration void operator=(const BikeStore& abikestore); abikestore is the argument from the right side of the = operator
Definition of = The definition of = for BikeStore could be: void BikeStore::operator = (const BikeStore& abikestore) { cout << "Calling Assignment Operator\n"; if(mcapacity < abikestore.mcapacity) { delete [] minventory; minventory = new Bike[aBikeStore.mCapacity]; mcapacity = abikestore.mcapacity; } for(int i = 0; i < abikestore.msize; i++) { minventory[i] = abikestore.minventory[i]; } msize = abikestore.msize; } // not a complete version of the assignment operator
Side affects In the previous slide the assignment operator is defined as a void function. Defining the assignment operator as a void function stops us from writing code like: BikeStore a; BikeStore b; BikeStore c; c = b = a; // this would not work because we ve defined the operator as // void function we must return a BikeStore for this to work