C++ More on Classes

If we can use STL containers (Vectors, Maps, etc )/ algorithms to solve any problem do them. Containers handle their own memory

But, let’s go through some class design where we have to do the memory management on a container ourself

Rule of Three

  • the Rule of Three is used for any object that dynamically allocates memory. Will need

    • Copy Constructor
    • Assignment Operator (Overloaded)
    • Destructor
  • Rule: If you need one, then you need all three!!!!

Copy Constructor in C++

  • Is a member function that initializes an object using another object of the same class.

General Syntax for the Header File: * Part of the construction of the Class

ClassName(const ClassName &old_object);

General Syntax for the Function

ClassName::ClassName(const ClassName &old_object )
{
    // Body of the Constructor
}

Destructors in C++

  • A Destructor is an instance member function which is invoked automatically whenever an object is going to destroy

Basis Syntax:

~constructor-name();

Properties

Destructor function is automatically invoked when the objects are destroyed.

When do you destructor called?

A destructor function is called automatically when the object goes out of scope: (1) the function ends (2) the program ends (3) a block containing local variables ends (4) a delete operator is called

Example of Rule of Three

Header File


#ifndef GCHARACTER_H_
#define GCHARACTER_H_

#include <iostream>
#include <string>

class GCharacter
{
    public:

        static const int DEFAULT_CAPACITY = 5;

        // Constructor
        GCharacter(std::string name = "John", int capacity = DEFAULT_CAPACITY );

        // Copy Constructor
        GCharacter(const GCharacter& source);

        // Overloaded Assignment 
        GCharacter& operator = (const GCharacter& source);

        // Destructor (easy to write)
        ~GCharacter();

        // Insert (member function) a new tool into the tool array (holder)
        void insert(const std::string& toolName);
    
    private:
        // Data Members
        std::string name;
        int capacity;
        int used;
        std::string* toolHolder;

    // Overloading the << operator (syntax)
    friend std::ostream& operator <<(std::ostream& os, const GCharacter& gc);

};


#endif

Functions File

#include<iostream>
using std::cout; using std::endl; 
#include<string>
using std::string;
#include<algorithm>
using std::copy;

#include "GCharacter.h"


// Constructor

GCharacter::GCharacter(string n, int cap)
{
    name = n;
    capacity = cap;
    used = 0;
    toolHolder = new string[cap];
}

// Copy Constructor

GCharacter::GCharacter(const GCharacter& source)
{
    cout << "Copy Constructor Called. " << endl;

    this->name  = source.name;
    this->capacity = source.capacity;
    used = source.used;
    toolHolder = new string[source.capacity]; // In order to do a  Deep Copy, we need to create a brand new string array

    copy(source.toolHolder, source.toolHolder + used, this->toolHolder );
}

// Overloaded Assignment Operator

GCharacter& GCharacter::operator=(const GCharacter& source)
{
    // Testing for self-assignment
    cout << "Overloaded Assignment Called. " << endl;
    
    // Check for self assignment
    // gc1 = gc1;
    if (this == &source)
    {
        return *this; // return GCharacter Object
    }
    this->name  = source.name;
    this->capacity = source.capacity;
    used = source.used;

    copy(source.toolHolder, source.toolHolder + used, this->toolHolder );

    return *this; // return GCharacter Object
}


// Destructor

GCharacter::~GCharacter()
{
    // only handling the dynamic memory
    cout << "Destructor called for " << this->name << " at this memory location " << this << endl;

    delete[] toolHolder; 
}

// Inserting a new tool into our toolHolder

void GCharacter::insert(const string& toolName)
{
    if (used == capacity) 
    {
        cout << "Tool Holder is full. Cannot add any additional tools " << endl;
    }
    else
    {
        toolHolder[used] = toolName;
        used++; //increment
    }

}

std::ostream& operator<<(std::ostream& os, const GCharacter& gc)
{
    os << "Game Character " << gc.name << " has the following tools: " << std::endl; 
    
    // iterate over our tool array

    for(int i = 0; i < gc.used; i++)
    {
        os << gc.toolHolder[i] + " | ";
    }

    return os << std::endl;
}

Main file

#include <iostream>
using std::cout;
using std::endl;

#include "GCharacter.h"


GCharacter exCopyConstructor(GCharacter tempGC)
{
    cout << "Copy Constructor called twice." << endl;
    cout << "Once for the formal parameter being passed by value" << endl;
    cout << "Once for the return value." << endl; 

    return tempGC;
}


int main()
{
    GCharacter gc1;

    gc1.insert("Potion");
    gc1.insert("Crossbow");
    gc1.insert("Gun");
    gc1.insert("Ray Gun");
    gc1.insert("Cloak");
    gc1.insert("Sword"); // won't get added in since we have a tool array of 5 by default

    cout << endl;
    cout << gc1 << endl;
    cout << endl;

    GCharacter gc2("Devin", 5); // 5 size for the tool array


    gc2.insert("Axe");

    exCopyConstructor(gc2);

    
    // Copy Constructor 
    GCharacter gc3 = gc2; // gc3 wasn't in exisitence

    // Over loaded Assignment Operator
    gc2 = gc1;

    // overloaded insertion operator
    cout << "_____________________ " << endl;
    cout << "gc2: " << gc2 << endl;
    cout << "gc1: " << gc1 << endl;
    cout << "gc3: " << gc3 << endl;

}

Output

Tool Holder is full. Cannot add any additional tools 

Game Character John has the following tools: 
Potion | Crossbow | Gun | Ray Gun | Cloak | 


Copy Constructor Called. 
Copy Constructor called twice.
Once for the formal parameter being passed by value
Once for the return value.
Copy Constructor Called. 
Destructor called for Devin at this memory location 0x7ffeef08f490
Destructor called for Devin at this memory location 0x7ffeef08f468
Copy Constructor Called. 
Overloaded Assignment Called. 
_____________________ 
gc2: Game Character John has the following tools: 
Potion | Crossbow | Gun | Ray Gun | Cloak | 

gc1: Game Character John has the following tools: 
Potion | Crossbow | Gun | Ray Gun | Cloak | 

gc3: Game Character Devin has the following tools: 
Axe | 

Destructor called for Devin at this memory location 0x7ffeef08f440
Destructor called for John at this memory location 0x7ffeef08f4e8
Destructor called for John at this memory location 0x7ffeef08f5c8

Composite Class

  • A composite class, is a class that is build by using the operations of other classes in the implementation (think of using a stack in a Graph)

Example with a Custom Stack Data Structure**

Header File (stack.h)

#ifndef STACK_H
#define STACK_H

using std::ostream;
#include<iostream>
using std::string;
#include<string>
#include<vector>
using std::vector;
#include<initializer_list>
using std::initializer_list;

class Stack{
 private:
  vector<char> vec_;
  
 public:
  Stack()=default;
  // Stack(size_t sz)
  Stack(initializer_list<char> c) : vec_(c) {};
  
  char top();
  void pop();
  void push(char);
  bool empty();
  // bool full();  // not a problem with vectors
  void clear();
  
  friend ostream& operator<<(ostream& out, const Stack &s);
};

ostream& operator<<(ostream& out, const Stack &s);

#endif

Stack Functions File (functions.cpp)

  • Includes basic methods of stacks including top, pop, push, empty, and clear
#include<algorithm>
using std::copy;
#include<iterator>
using std::ostream_iterator;
#include<stdexcept>
using std::underflow_error;

#include "stack.h"

char Stack::top(){
  if (vec_.size() == 0)
    throw underflow_error("top, empty stack");
  return vec_.back();
}

void Stack::pop(){
  if (vec_.size() == 0)
    throw underflow_error("pop, empty stack");
  vec_.pop_back();
}

void Stack::push(char s){
    vec_.push_back(s);
}

bool Stack::empty(){
    return vec_.empty();
}

void Stack::clear(){
  vec_.clear();
}

ostream& operator<<(ostream &out, const Stack &s){
    // Print the stack
    out << "(bottom) ";
    copy(s.vec_.begin(), s.vec_.end(), ostream_iterator<char>(out, ","));
    out << " (top)";
    return out;
}

Main Function to Run (main.cpp)

#include<iostream>
using std::cout; using std::endl; using std::cin;
#include<string>
using std::string;
#include<stdexcept>
using std::underflow_error;

#include "stack.h"

int main()

{
    // Practice with Basic Stack

    Stack stk = {'a','b','c','d','e'};

    cout << stk << endl;

    // Basic Stack Methods/Functions

    cout << "Top of the Stack: " << stk.top() << endl;

    stk.pop(); // Removes top value from the Stack

    stk.push('Z'); // Pushes another value onto the Stack

    cout << stk << endl;

    while (! stk.empty())
    {
        stk.pop();

    }

    cout << "After popping everything off the stack " << endl;
    cout << stk << endl;

    stk.push('D');
    stk.push('E');
    stk.push('V');
    stk.push('I');
    stk.push('N');
    
    cout << "After pushing items onto the stack: " << endl;

    cout << stk << endl;

    // Clear the stack

    stk.clear();

    cout << stk << endl;


    // Reverse a String

    string user_string = "", reverse_string = "";

    cout << "Input a string to reverse: ";
    getline(cin, user_string); // get line and put it into user_string

    Stack stk2;

    for (auto ch : user_string)
    {
        stk2.push(ch);
    } 

    cout << stk2 << endl;

    while (!stk2.empty())
    {
        stk2.pop();
    }


    cout << "Original String was: " << user_string << endl;
    cout << "Reversed String was: " << reverse_string << endl;


}

Output:

(bottom) a,b,c,d,e, (top)
Top of the Stack: e
(bottom) a,b,c,d,Z, (top)
After popping everything off the stack 
(bottom)  (top)
After pushing items onto the stack: 
(bottom) D,E,V,I,N, (top)
(bottom)  (top)
Input a string to reverse: Devin Powers
(bottom) D,e,v,i,n, ,P,o,w,e,r,s, (top)
Original String was: Devin Powers
Reversed String was: 

Bad Dynamic Memory Class

Fixing the Dynamic Memory

inserting an Image

Header File (stack.h)


#ifndef STACK_H
#define STACK_H

#include<iostream>
using std::ostream;
#include<initializer_list>
using std::initializer_list;

class Stack{
private:
    // Attributes
    char *ary_ = nullptr;  // This will be an array (The keyword nullptr denotes the pointer literal)
    int sz_ = 0;            // Size of the Array
    int top_ = -1; 

public:
    // General Constructors to Initialize the Stack
    Stack()=default; // Default constructor, when you create an object!
    
    Stack(size_t sz); // Takes in the "Size" of the String which will be converted into an Array -> string.size()
    Stack(initializer_list<char>); // Takes in {'A','B','C'} and Creates/Initializes the stack
    
    Stack(const Stack &);   // copy Constructor
    ~Stack();               // destructor (easy)
    Stack& operator=(const Stack &); // Overloaded =
    
    // Memebers/Operations on Stack
    char top();
    void pop();
    void push(char);
    bool empty();
    bool full();
    void clear();

    // Operator Overloader
    friend ostream& operator<< (ostream&, const Stack &);
};

ostream& operator<< (ostream&, const Stack&);
#endif

Functions File (stack.cpp)

inserting an Image

#include<string>
using std::string;
#include<iterator>
using std::ostream_iterator;
#include<algorithm>
using std::copy;
#include<initializer_list>
using std::initializer_list;
#include<stdexcept>
using std::overflow_error; using std::underflow_error;
#include<iostream>
using std::cout; using std::endl;


#include "stack.h"

// Copy Constructor
Stack::Stack(const Stack &s){
  //
    cout << "Using the Copy Constructor  " << endl;
    sz_ = s.sz_;
    top_ = s.top_;
    ary_= new char[s.sz_];
    // ary_ = s.ary_   BAD IDEA, just copies the pointers!!!
    copy(s.ary_, s.ary_+s.sz_, ary_);
}

// Delete Memory
Stack::~Stack(){
    cout << "Deleting Memory " << endl;
    delete [] ary_;
}

// classic, but there's a better way!!! ( = ) Overloaded Operator
Stack& Stack::operator=(const Stack &s){

  cout << "Using the Equal to (=) Operator " << endl;

    if (this != &s){  // this and s are not the same pointer!
	delete [] ary_;
	sz_ = s.sz_;
	top_ = s.top_;
	// is constructor call needed??
	ary_= new char[s.sz_];
	copy(s.ary_, s.ary_+s.sz_, ary_);
    }
    return *this;
}

// Pass Stack as a size
Stack::Stack(size_t sz){
  cout << "Using the size_t dataype constructor " << endl;
  sz_ = sz;
  ary_ = new char[sz];
  top_ = -1;
}

// Pass Stack as a list of char
Stack::Stack(initializer_list<char> c){
  // I think this badboy just takes a list into the constructor and automatically adds them to the array
  cout << "Using the initializer_list Constructor " << endl;
  sz_ = c.size();
  ary_ = new char[sz_];
  size_t indx = 0;
  top_ = sz_ - 1;
      
  for (auto e : c)
    ary_[indx++] = e;
}

// Methods to Perform on the Stack Object

char Stack::top(){
  if (top_ < 0)
    throw underflow_error("top, empty stack");
  return ary_[top_];
}

void Stack::pop(){
  if (top_ < 0)
    throw underflow_error("pop, empty stack");
  --top_;
}

void Stack::push(char element){
  // cout << "sz:"<<sz_<<" top:"<<top_<<" e:"<<element<<" sub:"<< top_ + (sz_ - 1) << endl;
  if (top_ >= (sz_ - 1) )
    throw overflow_error("push, full stack");
  ary_[++top_] = element;
}

bool Stack::empty(){
  return top_ < 0;
}
  
bool Stack::full(){
  return top_ >= ( sz_ - 1);
}

void Stack::clear(){
  top_ = -1;
}

// Print out the Stack Object

ostream& operator<<(ostream &out, const Stack &s){
    out << "(bottom) ";
    copy(s.ary_, s.ary_ + s.top_+1,
	 ostream_iterator<char>(out, ","));
    out << " (top)";
    return out;
}

Main File (main.cpp)

inserting an Image

#include<iostream>
using std::cout; using std::endl; using std::cin;
#include<string>
using std::string;
#include<stdexcept>
using std::overflow_error; using std::underflow_error;

#include "stack.h"

// Calls Copy Constructor to pass the Stack, then exercise the copy

void fn1(Stack s){

    // is the copy good?
    cout << "fn1 copied stack: " << s << endl;

    while (!s.empty())
    {
        s.pop();
    }

    for (int i = 0; i < 10; i++ )
    {
        s.push(static_cast<char>('z'-i));
    }
    cout << "Updated fn1 Stack: " << s << endl;
}

int main()
{
    // Using initializer_list<char> 
    Stack stk1 = {'a','b','c','d'}; 

    cout << stk1 << endl;

    // Lets test out some of the methods

    cout << "Top: " << stk1.top() << endl;

    stk1.top();
    // Can't push anymore values onto the stack because it's full
    //stk1.push('Z');

    cout << stk1 << endl;

    stk1.clear();

    cout << stk1 << endl;

    // Reverse a String

    string user_string = "", reverse_string = "";

    cout << "Input a String to reverse: ";

    getline(cin, user_string);

    // This is the size_t datatype constructor
    Stack rev_stk(user_string.size()); 

    cout << "User_String Size: " << user_string.size() << endl;

    for (auto ch : user_string )
    {
        // Push individual characters onto the stack
        rev_stk.push(ch);
    }

    // print out Rev_Stack

    cout << rev_stk << endl;


    Stack stack69(5); // Using the size_t datatype constructor

    stack69.push('A');
    stack69.push('B');
    stack69.push('C');
    stack69.push('D');
    stack69.push('E');


    cout << stack69 << endl;

    // Lets Create a New Object

    Stack stk2 = rev_stk;

    cout << "Stack Two: (Using the Copy Constructor) " << endl;
    cout << stk2 << endl;

    // stk1 is already in existence

    cout << "The '=' Operator Overload: " << endl;

    stk1 = rev_stk;

    cout << stk1 << endl;

    // Using the top function thing
    cout << "Testing the Top Function: (THIS WILL CALL THE COPY CONSTRUCTOR!!!!) " << endl;
    cout << "______________________________________________________________________" << endl;

    fn1(stk1); // Calls the Copy Constructor above

    cout << "STK1 After: " << endl; // It doesn't change the Stack

    cout << stk1 << endl;

}

Output:

Using the initializer_list Constructor 
(bottom) a,b,c,d, (top)
Top: d
(bottom) a,b,c,d, (top)
(bottom)  (top)
Input a String to reverse: Devin Powers Practice Test
Using the size_t dataype constructor 
User_String Size: 26
(bottom) D,e,v,i,n, ,P,o,w,e,r,s, ,P,r,a,c,t,i,c,e, ,T,e,s,t, (top)
Using the size_t dataype constructor 
(bottom) A,B,C,D,E, (top)
Using the Copy Constructor  
Stack Two: (Using the Copy Constructor) 
(bottom) D,e,v,i,n, ,P,o,w,e,r,s, ,P,r,a,c,t,i,c,e, ,T,e,s,t, (top)
The '=' Operator Overload: 
Using the Equal to (=) Operator 
(bottom) D,e,v,i,n, ,P,o,w,e,r,s, ,P,r,a,c,t,i,c,e, ,T,e,s,t, (top)
Testing the Top Function: (THIS WILL CALL THE COPY CONSTRUCTOR!!!!) 
______________________________________________________________________
Using the Copy Constructor  
fn1 copied stack: (bottom) D,e,v,i,n, ,P,o,w,e,r,s, ,P,r,a,c,t,i,c,e, ,T,e,s,t, (top)
Updated fn1 Stack: (bottom) z,y,x,w,v,u,t,s,r,q, (top)
Deleting Memory 
STK1 After: 
(bottom) D,e,v,i,n, ,P,o,w,e,r,s, ,P,r,a,c,t,i,c,e, ,T,e,s,t, (top)
Deleting Memory 
Deleting Memory 
Deleting Memory 
Deleting Memory 

Extra Stuff on the above example:

  • Why doesnt the stack object change after it is passed to the function fn1 above in the main.cpp file?

inserting an Image

Other

inserting an Image inserting an Image inserting an Image

Copy and Swap Idiom

  • Last Example working with stack class above

Templates and Classes

Remember template functions that we did?

  • By writing a function as a template, we can write generic function (for all types!)

It would be inconvenient to write a class/container that can only take on kind of data type

Template Stack Example

  • For a stack class like we’ve done previously above!

template<typename T >
class Stack {
  private:
      vector<T> vec_;
  
  public:

      T top();      // Return the data type, which is templatized!
      void pop();
      void push(T);
      bool empty();
      friend ostream& operator<<(ostream& out, const Stack<T>  &s);
}

Now, how would we call this stack object with various datatypes??

Stack<char> stk_c;
Stack<long> stk_l;
Stack<int> stk_i;
  • Like we did with templatized functions we put the datatype we want to work with in <>, in which will make a new class with that data type

Note that a template is not a class and it’s just a way to make a class where the type is *independent, where we can create many versions of the class with each template type

  • Everything in a templated container goes in the header file

  • You do not need to provide the template variables in the class definition itself for the class

Templated Friends

  • Theres a problem matching the template of a friend function with the templating of the class

  • There’s a hard way and an easy way

Easy

  • Do the friend inline in the class declaration

Hard

  • Just don’t do this way