8. Additional Class Techniques

About this Tutorial –

Objectives –

This course is aimed at students who need to get up to speed in C++. The course introduces object-oriented concepts and shows how they are implemented in C++. The course does not require awareness or familiarity with object-oriented programming techniques, but programming experience would be useful but not necessarily required.

Audience

Students who are new to object orientation (or programming) and need to learn C++.

Prerequisites

No previous experience in C++ programming is required. But any experience you do have in programming will help. Also no experience in Visual Studio is required. But again any experience you do have with programming development environments will be a valuable.

Contents

The C++ course covers these topics and more:

  • Introduction to C++: Key features of C++; Defining variables; Formulating expressions and statements; Built-in data types; Console input/output
  • Operators and types: Assignment; Compound Assignment; Increment and decrement operators; Const declarations; Type conversions
  • Going Further with Data Types: Enumerations; Arrays; Using the standard vector class; Using the standard string class; Structures

Download Solutions

Java tutorial


Overview

Estimated Time – 0.5 Hours

Not what you are looking? Try the next tutorial – Dynamic Arrays and Vectors

Lab 1: Construction and destruction

Lab 1: Construction and destruction
  1. A Reminder about Constructors
    • A constructor is a special initialization function in a class
      • Same name as class
      • Might take parameters, but has no return type
      • Invoked implicitly by the compiler, to guarantee an object is properly initialization when created
    • Here’s a simple example from before:
      // Header
      class BankAccount
      {
       string accountHolder;
       int id;
       double balance;
       ...
      public:
       BankAccount(string ah);
       ...
      };
      // Cpp File
      #include "BankAccount.h"
      BankAccount::BankAccount(string ah)
      {
       accountHolder = ah;
       id = -1;
       balance = 0;
      }
  2. Member Initialization Lists
    • For constructors, there’s an alternative syntax for initializing members
      • Known as a “member initialization list”
      • This is actually the preferred way to perform initialization, the reasons will become apparent in due course
    • This is the syntax for a member initialization list
      • You use member initialization lists on the function implementation (not on the function prototype)
        #include "BankAccount.h"
        BankAccount::BankAccount(string ah) : accountHolder(ah), id(-1), balance(0)
        {
         // Do any additional "algorithmic" initialization here if necessary
         cout << "I have just initialized " << accountHolder << endl; }
  3. Destructors
    • A destructor is a special "on destruction" function in a class
      • Same name as class, preceded by ~
      • No parameters, no return type
      • Invoked implicitly by the compiler just before an object is destroyed, to allow the object to do any additional tidying up
    • Here's a simple example:
      // Header
      class BankAccount
      {
       ...
      public:
       ~BankAccount();
       ..
      };
      // Cpp File
      #include "BankAccount.h"
      BankAccount::~BankAccount()
      {
       cout << "Destroying account "     << accountHolder     << endl; }
Lab
  1. Defining a "starter" class
    • In Visual Studio, create a new C++ project named GoingFurtherWithClassesApp in the student folder
    • Implement a class named LoginDetails with the following members:
      • Data members for the user's username and password.
        // Header
        string username;
        string password;
      • A constructor, to initialize both data members.
        // header
        LoginDetails(const string & username, const string & password);
        // CPP
        LoginDetails::LoginDetails(const string & username, const string & password)
        : username(username), password(password){}
      • A Display() method, to display the username and password (for testing purposes!)
        // Header
        void Display() const;
        // CPP
        void LoginDetails::Display() const
        {
         cout << "Username: " << username << ", password: " << password << endl << endl; }
      • View code file.
      • View code file.
      • A ChangePassword() method. The method should take parameters specifying the current password and a new password. If the current password parameter is correct, the method should change the password to the new password
        // Header
        bool ChangePassword(const string & oldPassword, const string & newPassword);
        // CPP
        bool LoginDetails::ChangePassword(const string & oldPassword, const string & newPassword)
        {
         if (oldPassword == password)
         {
          password = newPassword;
          return true;
         }
         else
         {
          return false;
         }
        }
      • View code file.
      • View code file.
    • In main(),write some simple code to test your LoginDetails class.
      LoginDetails me("Tarzan", "password1");
      cout << "Result of invalid attempt to change password: " << me.ChangePassword("hurrumphy", "newpassword1") << endl; cout << "Result of valid attempt to change password: " << me.ChangePassword("password1", "newpassword1") << endl; me.Display();
    • View code file.

Lab 2: Static members

Lab 2: Static members
  1. Defining Static Variables
    • static variables belong to the class as a whole
      • Allocated once, before first usage of class
      • Remain allocated regardless of number of instances
    • You declare static members in the header file:
      class BankAccount
      {
      private:
       static int nextId;
      public:
       static const double OVERDRAFT_LIMIT;
       ...
      };
    • You must also define static members in the source file:
      int BankAccount::nextId = 1;
      const double BankAccount::OVERDRAFT_LIMIT = -1000;
  2. Using Static Variables
    • Client code can access public static variables via the class name
      • Use the scope resolution operator ::
        cout << "Overdraft limit is "   << BankAccount::OVERDRAFT_LIMIT << endl;
  3. Defining Static Methods
    • static methods implement class-wide behaviour
      • E.g. getters/setters for static variables
      • E.g. factory methods, responsible for creating instances
      • E.g. instance management, keeping track of all instances
    • Note:
      • A static method can only directly access static members of the class (i.e. not instance variables/methods)
      • This is because static methods don't receive a this pointer
        // Header
        class BankAccount
        {
        public:
         static int GetNextId();
         ...
        };
        // Cpp File
        int BankAccount::GetNextId()
        {
         return nextId;
        }
  4. Using Static Methods
    • Client code can invoke public static methods via the class name
      • Use the scope resolution operator ::
        cout << "The next account will have ID "   << BankAccount::GetNextId() << endl;
    • Note for Java/C# developers:
      • Don't use the . operator!
Lab
  1. Defining const data members
    • Exercise 2: Defining const data members
      Modify your LoginDetails class as follows:

      • Make the username a const data member
        const string username;
      • Experiment with different ways to set this member (e.g. in the constructor's member initialization list, in the constructor's body, in a separate method, etc.). Where are you allowed to set the const data member?
        // Example
        LoginDetails::LoginDetails(const string & username, const string & password): username(username), password(password)
        {
        // This statement fails to compile - can't modify const data member.
        // this->username = username;
        }
      • View code file.
      • View code file.

Lab 3: Dynamic object creation/deletion

Lab 3: Dynamic object creation/deletion
  1. Reminder about Creating Objects
    • We've already seen you can just create objects like this:
      void myFunc()
      {
       BankAccount myAcc;   
       BankAccount yourAcc("John");
       // Use the objects.
       myAcc.Withdraw(100);
       yourAcc.Deposit(200);
       ...
      }
  2. Creating Dynamic Objects
    • Sometimes you want an object to live beyond the end of the current function (!)
    • To achieve this effect, you must allocate the function dynamically via the new keyword
      • Allocates the object on the "heap"
      • Calls an appropriate constructor
      • Returns a pointer
        void myFunc()
        {
         BankAccount * pMyAcc = new BankAccount;      // Calls no-arg ctor.
         BankAccount * pYourAcc = new BankAccount("John"); // Calls parameterized ctor.
         // Use the objects, via pointers now!
         pMyAcc->Withdraw(100);
         pYourAcc->Deposit(200);
         ...
        }
  3. Deleting Dynamic Objects
    • If you create an object using new, somewhere you must remember to delete it yourself
      • Use delete, followed by the pointer to the object to be deleted
      • Calls the object's destructor
      • Then frees the object's memory on the heap
        BankAccount *pMyAcc = 0;
        BankAccount *pYourAcc = 0;
        // Allocate objects via "new", and store addresses in pMyAcc and pYourAcc.
        ...
        // When we are ready, delete the objects via the pointers.
        delete pMyAcc;
        delete pYourAcc;
    • Note for Java/C# developers:
Lab
  1. Defining static members
    • Modify your LoginDetails class as follows:
      • Define a class-wide (i.e. static) data member to hold a "master" password. This is like a master key - it allows anyone to see anyone else's password (like an administrator). Set this data member to the magic word, i.e. "please"
        //Header
        static string masterPassword;
        // CPP
        string LoginDetails::masterPassword = "please";
      • Define another class-wide (i.e. static) data member to hold a "default" password.
        // Header
        static string defaultPassword;
        // CPP
        string LoginDetails::defaultPassword = "defaultPassword";
      • Allow the user to create a LoginDetails object by just specifying their username (tell them their default password)
        LoginDetails(const string & username, const string & password = defaultPassword);
      • Define a class-wide (i.e. static) method to display the "master" and "default" passwords
        // Header
        static void DisplayClassPasswords();
        // CPP
        cout << "Master password: " << masterPassword << endl; cout << "Default password: " << defaultPassword << endl;
      • View code file.
      • View code file.
    • Test the new functionality from main()
      LoginDetails you("Jane");
      you.Display();
      LoginDetails::DisplayClassPasswords();
    • View code file.

Lab 4: Miscellaneous class techniques

Lab 4: Miscellaneous class techniques
  1. Member Functions
    • C++ supports the notion of "const member functions"
      • i.e. member functions that promise not to modify the target object
      • Useful for "accessor" methods in your class, e.g. "getter" methods, "display" methods", etc.
    • The compiler checks that a const member function really doesn't modify the target object
      • If it does, you'll get a compiler error!
    • Const member functions are really important!!!
      • Every time you add a new member function to a class, ask yourself "should I declare this as a const member function?"
      • It makes it much easier for the client programmer to know which functions are likely to change their objects
    • To define a const member function:
      • In the class declaration, append the const keyword after the closing ) in the method signature
        class BankAccount
        {
         double GetBalance() const;
         ...
        };
      • You must also add the const keyword after the closing ) in the method implementation
        double BankAccount::GetBalance() const
        {
         // You have read-only access to members of "this" object here...
         return balance;
         // But you'd get a compiler error if you changed any member of "this" object...
         // balance += 100; NO!!!
        }
  2. Inline Functions
    • Up until now, you've implemented all functions in .cpp files
      double BankAccount::GetBalance() const
      {
       return balance;
      }
    • When the client program calls these functions
      • Flow-of-control passes into the function
      • Parameters are passed into the function if necessary
      • The function does its thing
      • After the } at the end of the function, flow of control returns to the caller function
    • For very small functions (like "getters")
      • The amount of effort getting into the function and then returning from the function far outweighs the amount of time spent inside the function itself!
    • In this case, you can declare the function "inline"
      • Tells the compiler "don't actually jump into this function at runtime, instead paste the body of the function inline into the client code"
      • Avoids the overhead of a function call / return
      • Appropriate for the very smallest of functions (typically the getters)
    • Preferred way to declare an inline function:
      class BankAccount
      {
       double GetBalance() const;
       ...
      };
      inline double BankAccount::GetBalance() const
      {
       return balance;
      }
    • Alternative approach (familiar for Java/C# developers):
      class BankAccount
      {
       double GetBalance() const
       {
        return balance;
       }
       ...
      };
  3. Nested Classes
    • C++ allows you to define nested classes
      • The nested class can be public, private, or protected
        class List
        {
        public:
         class Node
         {
         private:
          int  data;
          Node *pNext;
          Node *pPrev;
         public:
          Node(int data, Node *pNext, Node *pPrev);
          int GetData() const;
         };
         List();
         ~List();
         void Append(int data);
         const Node * GetHeadNode() const;
         int PopHeadData();
        private:
         Node * pHead;
        };

Lab 5: Exception handling

Lab 5: Exception handling
  1. Overview of Exceptions
    • Exceptions are a run-time mechanism for indicating exceptional conditions in C++
      • If you detect an exceptional condition, you can throw an exception
      • An exception is an object that contains relevant error info
    • Somewhere up the call stack, the exception is caught and dealt with
      • If the exception is not caught, your application terminates
    • Note: C++ doesn't support "checked exception"
      • A method doesn't have to declare what exceptions it might throw
      • Client code isn't obliged (by the compiler) to catch any exceptions
  2. Standard Exceptions in C++
    • C++ defines a simple set of standard exception classes
      • All in the std namespace
      • T8P1

  3. A closer Look at the exception Class
    • exception is the root exception class in C++
      • All the standard C++ exception classes inherit from exception
      • Custom exception classes should inherit from exception too
        class exception
        {
        public:
         exception() throw();
         exception(const exception & innerException) throw();
         virtual ~exception() throw();
         exception & operator=(const exception & other) throw();
         virtual const char * what() const throw();
         ...
        };
    • Note:
      • The throw() clause at the end of a method signature indicates the exceptions that might be thrown by the method
      • This is a run-time mechanism, not enforced at compile-time
  4. How to Throw and Catch Exceptions
    • try block
      • Contains code that might cause an exception
    • throw keyword
      • Throws an exception object
    • catch block(s)
      • One or more
      • Specify an exception class, to catch exception object
      • Perform recovery code
        try
        {
         // Code that might cause an exception ...
         throw SomeExceptionType(someParams);
        }
        catch (ExceptionType1 & ex)
        {
         // Code to handle ExceptionType1 ...
        }
        catch (ExceptionType2 & ex)
        {
         // Code to handle ExceptionType2 ...
        }
    • Note:
      • There is no concept of finally blocks in C++
      • achieve exception safety, implement appropriate destructors
  5. Catching a Hierarchy of Exceptions
    • When you define a catch handler...
      • It will catch that exception type, plus any subclasses
    • You can define multiple catch handlers
      • When an exception occurs, catch handlers are tried in sequence, looking for a suitable handler
      • You should organize your catch handlers from specific to general
        try
        {
         ...
        }
        catch (bad_alloc & ex)
        {
         ...
        }
        catch (exception & ex)
        {
         ...
        }
  6. Catching any Type of Exception
    • You can define a catch-all handler
      • Define a catch handler with ... as the exception type
        try
        {
         ...
        }
        catch (ExceptionType1 & ex)
        {
         ...
        }
        catch (ExceptionType2 & ex)
        {
         ...
        }
        catch (...)  // Catch-all handler, note the ... here is real syntax!
        {
         // Do general-purpose exception processing.
        }
  7. Re-throwing an Exception
    • Imagine you're writing a low-level library class...
      • Lots of exceptions might occur
      • You want to catch the exception locally, to do some intermediate processing (e.g. log the exception)
      • You also want to force the caller to do "full" processing
    • You can catch and re-throw an exception:
      • Do some local processing
      • The re-throw the same exception, using just the throw keyword
        try
        {
         ..
        }
        catch (ExceptionType & ex)
        {
         ...
         throw;
        }
  8. Throwing a Different Exception
    • It's usually desirable to shield the client programmer from low-level exceptions
      • Too much low-level grunge detail!
    • A better approach is as follows:
      • Catch low-level exception(s)
      • Throw higher-level exception(s) instead
        try
        {
         ...
        }
        catch (LowLevelExceptionType & ex)
        {
         ...
         throw HigherLevelExceptionType("Some high-level error message...");
        }

 

Well done. You have completed the tutorial in the C++ course. The next tutorial is

9. Dynamic Arrays and Vectors


Back to beginning
Copyright © 2016 TalkIT®





If you liked this post, please comment with your suggestions to help others.
If you would like to see more content like this in the future, please fill-in our quick survey.
Scroll to Top