13. Copying and Assignment

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 – Advanced Overloading Techniques

Lab 1: Converting constructors

Lab 1: Converting constructors
  1. Defining a Converting Constructor
    • Recall that a class can have any number of constructors
      • Taking various combinations of parameters
      • Giving the client various ways to initialize an object
    • If you have a constructor that takes a single argument
      • It’s known as a converting constructor
    • Example:
      • This constructor creates a string object from a const char *
      • Or put another way, it “converts” a const char * to a string
        class string
        {
        public:
         string(const char * text);
         ...
        };
  2. Calls to a Converting Constructor
    • If we have the constructor from the previous slide…
      • The compiler will call it automatically if needed, to convert a const char * to a string object
    • Example:
      • This method expects a string object (by reference)
        void myPrintString(const string & str)
        {
         cout << "Here's a string : " << str; }
      • The client code can pass in a string object or a const char *
        • If the client passes a const char *...
        • ... the compiler creates a temporary string object (via the constructor)
        • ... and then passes the temporary string object into myPrintString()
          myPrintString("Super Swans"); // Compiler converts literal text into string
Lab
  1. Defining the RefCountedString class
    • In Visual Studio, create a new C++ project named CopyingAssignmentApp in the student folder
    • Define a class named RefCountedString as follows:
      • The class can actually point to a standard C++ string internally, to hold the text
        string actualString;
      • The class also needs a reference count (set to 1 initially) to keep count of how many RefCountedString objects point to the same C++ string object
        int refCount;
      • Add constructors to initialize a RefCountedString from a character literal (such as "hello") and from a standard C++ string
        // Default constructor, to hold brand new text (or no text).
        RefCountedString::RefCountedString(const char * text)
        {
         // Create a new representation object, to hold brand new text (for the first time).
         pRep = new RefCountedStringRep(text);
        }
        // Copy constructor, to share text with an existing RefCountedString.
        RefCountedString::RefCountedString(const RefCountedString & other)
        {
         // Just use the same representation object as the other RefCountedString.
         // This will just increment an internal ref count, rather than doing any actual copying.
         HookExistingRep(other.pRep);
        }
        // Assignment operator, to share text with a different existing RefCountedString.
        RefCountedString & RefCountedString::operator = (const RefCountedString & other)
        {
         // Guard against assignment-to-self.
         if (this != &other)
         {
          // Unkook from existing representation object (this will decrement its ref count).
          UnhookExistingRep();
          // Then hook onto a different existing representation object (this will increment that one's ref count).
          HookExistingRep(other.pRep);
         }
         return *this;
        }
        // Destructor.
        RefCountedString::~RefCountedString()
        {
         // Just unhook ourself from our representation object.
         UnhookExistingRep();
        }
      • View code file.
    • In main(), create a RefCountedString object and test it behaves OK so far
      // Create a brand-new RefCountedString.
      RefCountedString fruit1("apple");
      cout << "Fruit1: " << fruit1 << endl; // Create another RefCountedString from an existing one. This calls the copy constructor. RefCountedString fruit2(fruit1); cout << "Fruit2: " << fruit2 << endl; // Create yet another RefCountedString from an existing one. This also calls the copy constructor. RefCountedString fruit3 = fruit2; cout << "Fruit3: " << fruit3 << endl << endl;
    • View code file.

Lab 2: Copying

Lab 2: Copying
  1. Overview of Copying
    • In C++, the word "copying" has a very specific meaning
      • "Initialize a new object from another object of the same type"
    • Examples
      • Imagine we already have these objects:
        date christmas(25, 12, 2012);
        date boxingDay(26, 12, 2012);
      • ... and this function (takes a date by value, not by ref!):
        void myPrintDate(date theDate) { ... }
      • Then copying occurs in these situations:
        date familyDay(christmas); // Copies "christmas" object into "familyDay".
        date footballDay = boxingDay; // Copies "boxingDay" object into "footballDay".
        myPrintDate(footballDay); // Copies "footballDay" object into func param
  2. Default Copying
    • By default, all classes have a default copy constructor
      • Takes an instance of the same type (by const-ref)...
      • Performs member-wise initialization
    • Example:
      • The default constructor for the date class is equivalent to the following code
      • Note, you wouldn't bother actually writing this yourself, the compiler would generate it anyway
        class date
        {
        private:
         int d, m, y;
        public:
         date(const date & other);
        ...
        };
        // Date
        date::date(const date & other) 
           : d(other.d),
            m(other.m),
            y(other.y)
        {}
  3. Problems with Default Copying
    • The default copy constructor doesn't work properly for some classes
      • Specifically, classes that have a pointer member
    • Example:
      • In our string class, the default copy constructor won't work
      • It just copies pointers, rather than copying the underlying data
        class string
        {
        private:
         char * text;
        public:
         string(const string & other);
        ...
        };
        // String
        string::string(const string & other)
            : text(other.text)
        {}
  4. Defining a Copy Constructor
    • For classes like our string class, we have to define a "proper" copy constructor
      • Otherwise the class doesn't work properly
    • Example:
      • Here's a copy constructor for our string class:
        class string
        {
        private:
         char * text;
        public:
         string(const string & other);
        ...
        };
        // Copy for String
        string::string(const string & other)
        {
         int len = strlen(other.text);
         text = new char[len + 1];
         strcpy(text, other.text);
        }
Lab
  1. Implementing some simple operators
    • Add some operators to the class, such as the following:
      • str1 == str2
        bool operator==(const RefCountedString & lhs, const RefCountedString & rhs)
        {
         return lhs.Equals(rhs);
        }
      • str1 != str2
        bool operator!=(const RefCountedString & lhs, const RefCountedString & rhs)
        {
         // You can always implement != as the inverse of ==.
         return !(lhs == rhs);
        }
      • str1 + str2
    • View code file.
    • In main(), write some simple code to test your operators
    • View code file.

Lab 3: Assignment

Lab 3: Assignment
  1. Overview of Assignment
    • The word "assignment" has a very specific meaning
      • "Assign an existing object (e.g. from another object of same type)"
    • Note the difference between copying and assignment:
      • Copying: initialization of a new object
      • Assignment: re-assignment of an existing object
    • Examples:
      • Imagine we already have these objects:
        date date1(19, 1, 2012);
        date date2(3, 12, 2012);
      • Now consider the following:
        date2 = date1; // Reassigns date2, with the value of date1.
  2. Default Assignment
    • By default, all classes have a default assignment operator
      • Takes an instance of the same type (by const-ref)...
      • Performs member-wise assignment
      • Returns itself (by reference)
    • Example:
      • The default assignment operator for a date class is equivalent to the following code
      • Note, you wouldn't bother actually writing this yourself
        class date
        {
        private:
         int d, m, y;
        public:
         date & operator=(const date & other);
         ...
        };
        //
        date & date::operator=(const date & other)
        {
         d = other.d;
         m = other.m;
         y = other.y;
         return *this;
        }
  3. Problems with Default Assignment
    • The default assignment operator doesn't work properly for some classes
      • Specifically, classes that have a pointer member
    • Example:
      • In our string class, the default assignment operator won't work
      • It just reassigns pointers, rather than copying the underlying data (and deleting the old data)
        class string
        {
        private:
         char * text;
        public:
         string & operator=(const string & other);
         ...
        };
        //
        string & string::operator=(const string & other) 
        {
         text = other.text;
         return *this;
        }
  4. Defining an Assignment Operator
    • For classes like our string class, we have to define a "proper" assignment operator
      • Otherwise the class doesn't work properly
    • Example:
      • Here's an assignment operator for our string class:
        class string
        {
        private:
         char * text;
        public:
         string & operator=(const string & other);
         ...
        };
        // Assignment
        string & string::operator=(const string & other) 
        {
         // Delete our old text.
         delete [] text;
         // Allocate memory for new text.
         int len = strlen(other.text);
         text = new char[len + 1];
         // Copy in new text.
         strcpy(text, other.text);
         // Finally, return reference to ourself.
         return *this;
        }
Lab
  1. Implementing copy-on-write semantics
    • Add some methods and operators that would modify a RefCountedString, such as the following:
      • str.ToUpperCase()
        // Convert our string to uppercase.
        RefCountedString & RefCountedString::ToUpperCase()
        {
         // Make sure we have a unique representation object.
         EnsureUniqueRep();
         // Convert our text to uppercase.
         strupr(const_cast(pRep->actualString.c_str()));
         return *this;
        }
      • View code file.
      • str.ToLowerCase()
        // Convert our string to lowercase.
        RefCountedString & RefCountedString::ToLowerCase()
        {
         // Make sure we have a unique representation object.
         EnsureUniqueRep();
         // Convert our text to lowercase.
         strlwr(const_cast(pRep->actualString.c_str()));
         return *this;
        }
      • View code file.
      • str1 += str2
        // Append text onto end of our text.
        RefCountedString & RefCountedString::Append(const RefCountedString & other)
        {
         // Make sure we have a unique representation object.
         EnsureUniqueRep();
         // Create a representation object holding appended string.
         pRep->actualString += other.pRep->actualString;
         return *this;
        }
        // Append text onto end of our text, using += syntax.
        RefCountedString & RefCountedString::operator +=(const RefCountedString & other)
        {
         return Append(other);
        }
      • View code file.
      • str1.SetAt(pos, 'A')
        // Set character at specified index position.
        RefCountedString & RefCountedString::SetAt(int index, char ch)
        {
         // Make sure we have a unique representation object, which we can modify with impunity :-)
         EnsureUniqueRep();
         // Set character at specified position.
         pRep->actualString[index] = ch;
         return *this;
        }
      • View code file.
      • str1 = str2
        bool RefCountedString::Equals(const RefCountedString & other) const
        {
         // The 1st test for equality is whether the RefCountedString objects share the same representation object.
         if (pRep == other.pRep)
          return true;
         // The 2nd test for equality is whether the RefCountedString objects happen to contain equal string objects.
         if (pRep->actualString == other.pRep->actualString)
          return true;
         // If we get here, the RefCountedString objects contain unequal string objects.
         return false;
        }
      • View code file.
    • Think carefully about these methods... when you modify one RefCountedString object, it should NOT modify any other RefCountedString objects that happen to have the same text at that moment. In other words, you need "copy-on-write" semantics (i.e. when a RefCountedString is modified, if the reference count is > 1, then you need to break off a separate copy that you can modify in isolation from all the other shared references)
    • You'll also need to implement a copy constructor and assignment operator with these sentiments in mind.
    • In main(), write some code to test your class thoroughly
      // Change fruit1. This should snap off a separate representation object.
      fruit1.ToUpperCase();
      cout << "After modifying fruit1... " << endl; cout << "Fruit1: " << fruit1 << endl; cout << "Fruit2: " << fruit2 << endl; cout << "Fruit3: " << fruit3 << endl << endl; // Let's see what's equal. cout << "fruit1==fruit2? " << (fruit1 == fruit2) << endl; cout << "fruit2==fruit3? " << (fruit2 == fruit3) << endl; cout << "fruit1==fruit3? " << (fruit1 == fruit3) << endl << endl; Etc...
    • View code file.

 

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

14. Advanced Overloading Techniques


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