Lesson 12. Operator Types and Operator Overloading

In addition to encapsulating data and methods, classes can also encapsulate operators that make it easy to operate on instances of this class. You can use these operators to perform operations such as assignment or addition on class objects similar to those on integers that you saw in Lesson 5, “Working with Expressions, Statements, and Operators.” Just like functions, operators can also be overloaded.

In this lesson, you learn:

Image Using the keyword operator

Image Unary and binary operators

Image Conversion operators

Image The move assignment operator

Image Operators that cannot be redefined

What Are Operators in C++?

On a syntactical level, there is very little that differentiates an operator from a function, save for the use of the keyword operator. An operator declaration looks quite like a function declaration:

return_type operator operator_symbol (...parameter list...);

The operator_symbol in this case could be any of the several operator types that the programmer can define. It could be + (addition) or && (logical AND) and so on. The operands help the compiler distinguish one operator from another. So, why does C++ provide operators when functions are also supported?

Consider a utility class Date that encapsulates the day, month, and year:

Date holiday (12, 25, 2016);  // initialized to Dec 25, 2016

Assuming that you want to add a day and get the instance to contain the next day—Dec 26, 2016—which of the following two options would be more intuitive?

Image Option 1 (using the increment operator):

++ holiday;

Image Option 2 (using a member function Increment()):

holiday.Increment();  // Dec 26, 2016

Clearly, Option 1 scores over method Increment(). The operator-based mechanism facilitates consumption by supplying ease of use and intuitiveness. Implementing operator (<) in class Date would help you compare two instances of class Date like this:

if(date1 < date2)
{
   // Do something
}
else
{
   // Do something else
}

Operators can be used in more situations than just classes that manage dates. An addition operator (+) in a string utility class such as MyString introduced to you in Listing 9.9 in Lesson 9, “Classes and Objects,” would facilitate easy concatenation:

MyString sayHello ("Hello ");
MyString sayWorld (" world");
MyString sumThem (sayHello + sayWorld); // if operator+ were supported by
MyString


Note

The effort in implementing relevant operators will be rewarded by the ease of consumption of the class.


On a broad level, operators in C++ can be classified into two types: unary operators and binary operators.

Unary Operators

As the name suggests, operators that function on a single operand are called unary operators. A unary operator that is implemented in the global namespace or as a static member function of a class uses the following structure:

return_type operator operator_type (parameter_type)
{
    // ... implementation
}

A unary operator that is a (non-static) member of a class has a similar structure but is lacking in parameters, because the single parameter that it works upon is the instance of the class itself (*this):

return_type operator operator_type ()
{
    // ... implementation
}

Types of Unary Operators

The unary operators that can be overloaded (or redefined) are shown in Table 12.1.

Image

TABLE 12.1 Unary Operators

Programming a Unary Increment/Decrement Operator

A unary prefix increment operator (++) can be programmed using the following syntax within the class declaration:

// Unary increment operator (prefix)
Date& operator ++ ()
{
    // operator implementation code
    return *this;
}

The postfix increment operator (++), on the other hand, has a different return type and an input parameter (that is not always used):

Date operator ++ (int)
{
   // Store a copy of the current state of the object, before incrementing day
   Date copy (*this);

   // increment implementation code

   // Return state before increment (because, postfix)
   return copy;
}

The prefix and postfix decrement operators have a similar syntax as the increment operators, just that the declaration would contain a -- where you see a ++. Listing 12.1 shows a simple class Date that allows incrementing days using operator (++).

LISTING 12.1 A Calendar Class That Handles Day, Month, and Year, and Allows Incrementing and Decrementing Days


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Date
  4: {
  5: private:
  6:    int day, month, year;
  7:
  8: public:
  9:    Date (int inMonth, int inDay, int inYear)
 10:         : month (inMonth), day(inDay), year (inYear) {};
 11:
 12:    Date& operator ++ () // prefix increment
 13:    {
 14:       ++day;
 15:       return *this;
 16:    }
 17:
 18:    Date& operator -- () // prefix decrement
 19:    {
 20:       --day;
 21:       return *this;
 22:    }
 23:
 24:    void DisplayDate()
 25:    {
 26:       cout << month << " / " << day << " / " << year << endl;
 27:    }
 28: };
 29:
 30: int main ()
 31: {
 32:    Date holiday (12, 25, 2016); // Dec 25, 2016
 33:
 34:    cout << "The date object is initialized to: ";
 35:    holiday.DisplayDate ();
 36:
 37:    ++holiday; // move date ahead by a day
 38:    cout << "Date after prefix-increment is: ";
 39:    holiday.DisplayDate ();
 40:
 41:    --holiday; // move date backwards by a day
 42:    cout << "Date after a prefix-decrement is: ";
 43:    holiday.DisplayDate ();
 44:
 45:    return 0;
 46: }


Output Image

The date object is initialized to: 12 / 25 / 2016
Date after prefix-increment is: 12 / 26 / 2016
Date after a prefix-decrement is: 12 / 25 / 2016

Analysis Image

The operators of interest defined in Lines 12 to 22, help in adding or subtracting a day at a time from instances of class Day, as shown in Lines 37 and 41 in main(). Prefix increment operators as demonstrated in this sample need to return a reference to the instance after completing the increment operation.


Note

This version of a date class has a bare minimum implementation to reduce lines and to explain how prefix operator (++) and operator (--) are to be implemented. A professional version of the same would implement rollover functionalities for month and year and take leap years into consideration as well.


To support postfix increment or decrement, you simply add the following code to class Date:

// postfix differs from prefix operator in return-type and parameters
Date operator ++ (int) // postfix increment
{
   Date copy(month, day, year);
   ++day;
   return copy; // copy of instance before increment returned
}

Date operator -- (int) // postfix decrement
{
   Date copy(month, day, year);
   --day;
   return copy; // copy of instance before decrement returned
}

When your version of class Date supports both prefix and postfix increment and decrement operators, you will be able to use objects of the class using the following syntax:

Date holiday (12, 25, 2016);  // instantiate
++ holiday;  // using prefix increment operator++
holiday ++;  // using postfix increment operator++
-- holiday;  // using prefix decrement operator --
holiday --;  // using postfix decrement operator --


Note

As the implementation of the postfix operators demonstrates, a copy containing the existing state of the object is created before the increment or decrement operation to be returned thereafter.

In other words, if you had the choice between using ++ object; and object ++; to essentially only increment, you should choose the former to avoid the creation of a temporary copy that will not be used.


Programming Conversion Operators

If you use Listing 12.1 and insert the following line in main():

cout << holiday;  // error in absence of conversion operator

The code would result in the following compile failure: error: binary '<<' : no operator found which takes a right-hand operand of type 'Date' (or there is no acceptable conversion). This error essentially indicates that cout doesn’t know how to interpret an instance of Date as class Date does not support the operators that convert its contents into a type that cout would accept.

We know that cout can work well with a const char*:

std::cout << "Hello world"; // const char* works!

So, getting cout to work with an instance of type Date might be as simple as adding an operator that returns a const char* version:

operator const char*()
{
   // operator implementation that returns a char*
}

Listing 12.2 is a simple implementation of this conversion operator.

LISTING 12.2 Implementing Conversion operator const char* for class Date


  0: #include <iostream>
  1: #include <sstream> // new include for ostringstream
  2: #include <string>
  3: using namespace std;
  4:
  5: class Date
  6: {
  7: private:
  8:    int day, month, year;
  9:    string dateInString;
 10:
 11: public:
 12:    Date(int inMonth, int inDay, int inYear)
 13:       : month(inMonth), day(inDay), year(inYear) {};
 14:
 15:    operator const char*()
 16:    {
 17:       ostringstream formattedDate; // assists string construction
 18:       formattedDate << month << " / " << day << " / " << year;
 19:
 20:       dateInString = formattedDate.str();
 21:       return dateInString.c_str();
 22:    }
 23: };
 24:
 25: int main ()
 26: {
 27:    Date Holiday (12, 25, 2016);
 28:
 29:    cout << "Holiday is on: " << Holiday << endl;
 30:
 31:    // string strHoliday (Holiday); // OK!
 32:    // strHoliday = Date(11, 11, 2016); // also OK!
 33:
 34:    return 0;
 35: }


Output Image

Holiday is on: 12 / 25 / 2016

Analysis Image

The benefit of implementing operator const char* as shown in Lines 15 to 23 is visible in Line 29 in main(). Now, an instance of class Date can directly be used in a cout statement, taking advantage of the fact that cout understands const char*. The compiler automatically uses the output of the appropriate (and in this case, the only available) operator in feeding it to cout that displays the date on the screen. In your implementation of operator const char*, you use std::ostringstream to convert the member integers into a std::string object as shown in Line 18. You could’ve directly returned formattedDate.str(), yet you store a copy in private member Date::dateInString in Line 20 because formattedDate being a local variable is destroyed when the operator returns. So, the pointer got via str() would be invalidated on function return.

This operator opens up new possibilities toward consuming class Date. It allows you to even assign an instance of a Date directly to a string:

string strHoliday (holiday);
strHoliday = Date(11, 11, 2016);


Caution

Note that such assignments cause implicit conversions, that is, the compiler has used the available conversion operator (in this case const char*) thereby permitting unintended assignments that get compiled without error. To avoid implicit conversions, use keyword explicit at the beginning of an operator declaration, as follows:

explicit operator const char*()
{
   // conversion code here
}

Using explicit would force the programmer to assert his intention to convert using a cast:

string strHoliday(static_cast<const char*>(Holiday));
strHoliday=static_cast<const char*>(Date(11,11,2016));

Casting, including static_cast, is discussed in detail in Lesson 13, “Casting Operators.



Note

Program as many operators as you think your class would be used with. If your application needs an integer representation of a Date, then you may program it as follows:

explicit operator int()
{
   return day + month + year;
}

This would allow an instance of Date to be used or transacted as an integer:

FuncTakesInt(static_cast<int>(Date(12, 25, 2016)));

Listing 12.8 later in this lesson also demonstrates conversion operators used with a string class.


Programming Dereference Operator (*) and Member Selection Operator (->)

The dereference operator (*) and member selection operator (->) are most frequently used in the programming of smart pointer classes. Smart pointers are utility classes that wrap regular pointers and simplify memory management by resolving ownership and copy issues using operators. In some cases, they can even help improve the performance of the application. Smart pointers are discussed in detail in Lesson 26, “Understanding Smart Pointers.” This lesson takes a brief look at how overloading operators helps in making smart pointers work.

Analyze the use of the std::unique_ptr in Listing 12.3 and understand how it uses operator (*) and operator (->) to help you use the smart pointer class like any normal pointer.

LISTING 12.3 Using Smart Pointer unique_ptr to Manage a Dynamically Allocated Instance of class Date


  0: #include <iostream>
  1: #include <memory>  // new include to use unique_ptr
  2: using namespace std;
  3:
  4: class Date
  5: {
  6: private:
  7:    int day, month, year;
  8:    string dateInString;
  9:
 10: public:
 11:    Date(int inMonth, int inDay, int inYear)
 12:       : month(inMonth), day(inDay), year(inYear) {};
 13:
 14:    void DisplayDate()
 15:    {
 16:       cout << month << " / " << day << " / " << year << endl;
 17:    }
 18: };
 19:
 20: int main()
 21: {
 22:    unique_ptr<int> smartIntPtr(new int);
 23:    *smartIntPtr = 42;
 24:
 25:    // Use smart pointer type like an int*
 26:    cout << "Integer value is: " << *smartIntPtr << endl;
 27:
 28:    unique_ptr<Date> smartHoliday (new Date(12, 25, 2016));
 29:    cout << "The new instance of date contains: ";
 30:
 31:    // use smartHoliday just as you would a Date*
 32:    smartHoliday->DisplayDate();
 33:
 34:    return 0;
 35: }


Output Image

Integer value is: 42
The new instance of date contains: 12 / 25 / 2016

Analysis Image

Line 22 is where you declare a smart pointer to type int. This line shows template initialization syntax for smart pointer class unique_ptr. Similarly, Line 28 declares a smart pointer to an instance of class Date. Focus on the pattern, and ignore the details for the moment.


Note

Don’t worry if this template syntax looks awkward because templates are introduced later in Lesson 14, “An Introduction to Macros and Templates.


This example demonstrates how a smart pointer allows you to use normal pointer syntax as shown in Lines 23 and 32. In Line 23, you are able to display the value of the int using *smartIntPtr, whereas in Line 32 you use smartHoliday->DisplayData() as if these two variables were an int* and Date*, respectively. The secret lies in the pointer class std::unique_ptr that is smart because it implements operator (*) and operator (->).


Note

Smart pointer classes can do a lot more than just parade around as normal pointers, or de-allocate memory when they go out of scope. Find out more about this topic in Lesson 26.

To see an implementation of a basic smart pointer class that has overloaded these operators, you may briefly visit Listing 26.1.


Binary Operators

Operators that function on two operands are called binary operators. The definition of a binary operator implemented as a global function or a static member function is the following:

return_type operator_type (parameter1, parameter2);

The definition of a binary operator implemented as a class member is

return_type operator_type (parameter);

The reason the class member version of a binary operator accepts only one parameter is that the second parameter is usually derived from the attributes of the class itself.

Types of Binary Operators

Table 12.2 contains binary operators that can be overloaded or redefined in your C++ application.

Image

TABLE 12.2 Overloadable Binary Operators

Programming Binary Addition (a+b) and Subtraction (a-b) Operators

Similar to the increment/decrement operators, the binary plus and minus, when defined, enable you to add or subtract the value of a supported data type from an object of the class that implements these operators. Take a look at your calendar class Date again. Although you have already implemented the capability to increment Date so that it moves the calendar one day forward, you still do not support the capability to move it, say, five days ahead. To do this, you need to implement binary operator (+), as the code in Listing 12.4 demonstrates.

LISTING 12.4 Calendar Class Featuring the Binary Addition Operator


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Date
  4: {
  5: private:
  6:    int day, month, year;
  7:    string dateInString;
  8:
  9: public:
 10:    Date(int inMonth, int inDay, int inYear)
 11:       : month(inMonth), day(inDay), year(inYear) {};
 12:
 13:    Date operator + (int daysToAdd) // binary addition
 14:    {
 15:       Date newDate (month, day + daysToAdd, year);
 16:       return newDate;
 17:    }
 18:
 19:    Date operator - (int daysToSub) // binary subtraction
 20:    {
 21:       return Date(month, day - daysToSub, year);
 22:    }
 23:
 24:    void DisplayDate()
 25:    {
 26:       cout << month << " / " << day << " / " << year << endl;
 27:    }
 28: };
 29:
 30: int main()
 31: {
 32:    Date Holiday (12, 25, 2016);
 33:    cout << "Holiday on: ";
 34:    Holiday.DisplayDate ();
 35:
 36:    Date PreviousHoliday (Holiday - 19);
 37:    cout << "Previous holiday on: ";
 38:    PreviousHoliday.DisplayDate();
 39:
 40:    Date NextHoliday(Holiday + 6);
 41:    cout << "Next holiday on: ";
 42:    NextHoliday.DisplayDate ();
 43:
 44:    return 0;
 45: }


Output Image

Holiday on: 12 / 25 / 2016
Previous holiday on: 12 / 6 / 2016
Next holiday on: 12 / 31 / 2016

Analysis Image

Lines 13 to 22 contain the implementations of the binary operator (+) and operator (-) that permit the use of simple addition and subtraction syntax as seen in main() in Lines 40 and 36, respectively.

The binary addition operator would also be useful in a string class. In Lesson 9, you analyze a simple string wrapper class MyString that encapsulates memory management, copying, and the like, as shown in Listing 9.9. This class MyString doesn’t support the concatenation of two strings using a simple syntax:

MyString Hello("Hello ");
MyString World(" World");
MyString HelloWorld(Hello + World);  // error: operator+ not defined

Defining this operator (+) makes using MyString extremely easy and is hence worth the effort:

MyString operator+ (const MyString& addThis)
{
   MyString newString;

   if (addThis.buffer != NULL)
   {
      newString.buffer = new char[GetLength() + strlen(addThis.buffer) + 1];
      strcpy(newString.buffer, buffer);
      strcat(newString.buffer, addThis.buffer);
   }

   return newString;
}

Add the preceding code to Listing 9.9 with a private default constructor MyString() with empty implementation to be able to use the addition syntax. You can see a version of class MyString with operator (+) among others in Listing 12.11 later in this lesson.

Implementing Addition Assignment (+=) and Subtraction Assignment (-=) Operators

The addition assignment operators allow syntax such as “a += b;” that allows the programmer to increment the value of an object a by an amount b. In doing this, the utility of the addition assignment operator is that it can be overloaded to accept different types of parameter b. Listing 12.5 that follows allows you to add an integer value to a Date object.

LISTING 12.5 Defining Operator (+=) and Operator (-=) to Add or Subtract Days in the Calendar Given an Integer Input


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Date
  4: {
  5: private:
  6:     int day, month, year;
  7:
  8: public:
  9:     Date(int inMonth, int inDay, int inYear)
 10:         : month(inMonth), day(inDay), year(inYear) {}
 11:
 12:    void operator+= (int daysToAdd) // addition assignment
 13:    {
 14:       day += daysToAdd;
 15:    }
 16:
 17:    void operator-= (int daysToSub) // subtraction assignment
 18:    {
 19:       day -= daysToSub;
 20:    }
 21:
 22:    void DisplayDate()
 23:    {
 24:        cout << month << " / " << day << " / " << year << endl;
 25:    }
 26: };
 27:
 28: int main()
 29: {
 30:    Date holiday (12, 25, 2016);
 31:    cout << "holiday is on: ";
 32:    holiday.DisplayDate ();
 33:
 34:    cout << "holiday -= 19 gives: ";
 35:    holiday -= 19;
 36:    holiday.DisplayDate();
 37:
 38:    cout << "holiday += 25 gives: ";
 39:    holiday += 25;
 40:    holiday.DisplayDate ();
 41:
 42:    return 0;
 43: }


Output Image

holiday is on: 12 / 25 / 2016
holiday -= 19 gives: 12 / 6 / 2016
holiday += 25 gives: 12 / 31 / 2016

Analysis Image

The addition and subtraction assignment operators of interest are in Lines 12 to 20. These allow adding and subtracting an integer value for days, as seen in main(), for instance:

35:    holiday -= 19;
39:    holiday += 25;

Your class Date now allows users to add or remove days from it as if they are dealing with integers using addition or subtraction assignment operators that take an int as a parameter. You can even provide overloaded versions of the addition assignment operator (+=) that work with an instance of a fictitious class Days:

// operator that adds a Days to an existing Date
void operator += (const Days& daysToAdd)
{
    day += daysToAdd.GetDays();
}


Note

The multiplication assignment *=, division assignment /=, modulus assignment %=, subtraction assignment -=, left-shift assignment <<=, right-shift assignment >>=, XOR assignment ^=, bitwise inclusive OR assignment |=, and bitwise AND assignment &= operators have a syntax similar to the addition assignment operator shown in Listing 12.5.

Although the ultimate objective of overloading operators is making the class easy and intuitive to use, there are many situations where implementing an operator might not make sense. For example, our calendar class Date has absolutely no use for a bitwise AND assignment &= operator. No user of this class should ever expect (or even think of) getting useful results from an operation such as greatDay &= 20;.


Overloading Equality (==) and Inequality (!=) Operators

What do you expect when the user of class Date compares one instance to another:

if (date1 == date2)
{
    // Do something
}
else
{
    // Do something else
}

In the absence of an equality operator ==, the compiler simply performs a binary comparison of the two objects and returns true when they are exactly identical. This binary comparison will work for instances of classes containing simple data types (like the Date class as of now), but it will not work if the class in question has a non-static string member (char*), such as MyString in Listing 9.9. When two instances of class MyString are compared, a binary comparison of the member attributes would actually compare the member string pointer values (MyString::buffer). These would not be equal even when the strings are identical in content. Comparisons involving two instances of MyString would return false consistently. You solve this problem by defining comparison operators. A generic expression of the equality operator is the following:

bool operator== (const ClassType& compareTo)
{
   // comparison code here, return true if equal else false
}

The inequality operator can reuse the equality operator:

bool operator!= (const ClassType& compareTo)
{
   // comparison code here, return true if inequal else false
}

The inequality operator can be the inverse (logical NOT) of the result of the equality operator. Listing 12.6 demonstrates comparison operators defined by our calendar class Date.

LISTING 12.6 Demonstrates Operators == and !=


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Date
  4: {
  5: private:
  6:     int day, month, year;
  7:
  8: public:
  9:     Date(int inMonth, int inDay, int inYear)
 10:         : month(inMonth), day(inDay), year(inYear) {}
 11:
 12:    bool operator== (const Date& compareTo)
 13:    {
 14:       return ((day == compareTo.day)
 15:             && (month == compareTo.month)
 16:            && (year == compareTo.year));
 17:    }
 18:
 19:    bool operator!= (const Date& compareTo)
 20:    {
 21:       return !(this->operator==(compareTo));
 22:    }
 23:
 24:    void DisplayDate()
 25:    {
 26:     cout << month << " / " << day << " / " << year << endl;
 27:    }
 28: };
 29:
 30: int main()
 31: {
 32:    Date holiday1 (12, 25, 2016);
 33:    Date holiday2 (12, 31, 2016);
 34:
 35:    cout << "holiday 1 is: ";
 36:    holiday1.DisplayDate();
 37:    cout << "holiday 2 is: ";
 38:    holiday2.DisplayDate();
 39:
 40:    if (holiday1 == holiday2)
 41:       cout << "Equality operator: The two are on the same day" << endl;
 42:    else
 43:       cout << "Equality operator: The two are on different days" << endl;
 44:
 45:    if (holiday1 != holiday2)
 46:       cout << "Inequality operator: The two are on different days" << endl;
 47:    else
 48:       cout << "Inequality operator: The two are on the same day" << endl;
 49:
 50:    return 0;
 51: }


Output Image

holiday 1 is: 12 / 25 / 2016
holiday 2 is: 12 / 31 / 2016
Equality operator: The two are on different days
Inequality operator: The two are on different days

Analysis Image

The equality operator (==) is a simple implementation that returns true if the day, month, and year are all equal, as shown in Lines 12 to 17. The inequality operator (!=) simply reuses the equality operator code as seen in Line 21. The presence of these operators helps compare two Date objects, holiday1 and holiday2, in main() in Lines 40 and 45.

Overloading <, >, <=, and >= Operators

The code in Listing 12.6 made the Date class intelligent enough to be able to tell whether two Date objects are equal or unequal. You need to program the less-than (<), greater-than (>), less-than-equals (<=), and greater-than-equals (>=) operators to enable conditional checking akin to the following:

if (date1 < date2) {// do something}

or

if (date1 <= date2) {// do something}

or

if (date1 > date2) {// do something}

or

if (date1 >= date2) {// do something}

These operators are demonstrated by the code shown in Listing 12.7.

LISTING 12.7 Demonstrates Implementing <, <=, >, and >= Operators


  0: #include <iostream>
  1: using namespace std;
  2:
  3: class Date
  4: {
  5: private:
  6:    int day, month, year;
  7:
  8: public:
  9:    Date(int inMonth, int inDay, int inYear)
 10:       : month(inMonth), day(inDay), year(inYear) {}
 11:
 12:    bool operator< (const Date& compareTo)
 13:    {
 14:       if (year < compareTo.year)
 15:          return true;
 16:       else if (month < compareTo.month)
 17:          return true;
 18:       else if (day < compareTo.day)
 19:          return true;
 20:       else
 21:          return false;
 22:    }
 23:
 24:    bool operator<= (const Date& compareTo)
 25:    {
 26:       if (this->operator== (compareTo))
 27:          return true;
 28:       else
 29:          return this->operator< (compareTo);
 30:    }
 31:
 32:    bool operator > (const Date& compareTo)
 33:    {
 34:       return !(this->operator<= (compareTo));
 35:    }
 36:
 37:    bool operator== (const Date& compareTo)
 38:    {
 39:       return ((day == compareTo.day)
 40:          && (month == compareTo.month)
 41:          && (year == compareTo.year));
 42:    }
 43:
 44:    bool operator>= (const Date& compareTo)
 45:    {
 46:       if(this->operator== (compareTo))
 47:          return true;
 48:       else
 49:          return this->operator> (compareTo);
 50:    }
 51:
 52:    void DisplayDate()
 53:    {
 54:       cout << month << " / " << day << " / " << year << endl;
 55:    }
 56: };
 57:
 58: int main()
 59: {
 60:    Date holiday1 (12, 25, 2016);
 61:    Date holiday2 (12, 31, 2016);
 62:
 63:    cout << "holiday 1 is: ";
 64:    holiday1.DisplayDate();
 65:    cout << "holiday 2 is: ";
 66:    holiday2.DisplayDate();
 67:
 68:    if (holiday1 < holiday2)
 69:       cout << "operator<: holiday1 happens first" << endl;
 70:
 71:    if (holiday2 > holiday1)
 72:       cout << "operator>: holiday2 happens later" << endl;
 73:
 74:    if (holiday1 <= holiday2)
 75:       cout << "operator<=: holiday1 happens on or before holiday2" << endl;
 76:
 77:    if (holiday2 >= holiday1)
 78:       cout << "operator>=: holiday2 happens on or after holiday1" << endl;
 79:
 80:    return 0;
 81: }


Output Image

holiday 1 is: 12 / 25 / 2016
holiday 2 is: 12 / 31 / 2016
operator<: holiday1 happens first
operator>: holiday2 happens later
operator<=: holiday1 happens on or before holiday2
operator>=: holiday2 happens on or after holiday1

Analysis Image

The operators of interest are implemented in Lines 12 to 50 and partially reuse operator (==) that you saw in Listing 12.6. The implementation of operators ==, <, and > has been consumed by the rest.

The operators have been consumed inside main() between Lines 68 and 78, which indicate how easy it now is to compare two different dates.

Overloading Copy Assignment Operator (=)

There are times when you want to assign the contents of an instance of a class to another, like this:

Date holiday(12, 25, 2016);
Date anotherHoliday(1, 1, 2017);
anotherHoliday = holiday; // uses copy assignment operator

This assignment invokes the default copy assignment operator that the compiler has built in to your class when you have not supplied one. Depending on the nature of your class, the default copy assignment operator might be inadequate, especially if your class is managing a resource that will not be copied. This problem with the default copy assignment operator is similar to the one with the default copy constructor discussed in Lesson 9. To ensure deep copies, as with the copy constructor, you need to specify an accompanying copy assignment operator:

ClassType& operator= (const ClassType& copySource)
{
   if(this != &copySource)  // protection against copy into self
   {
      // copy assignment operator implementation
   }
   return *this;
}

Deep copies are important if your class encapsulates a raw pointer, such as class MyString shown in Listing 9.9. To ensure deep copy during assignments, define a copy assignment operator as shown in Listing 12.8.

LISTING 12.8 A Better class MyString from Listing 9.9 with a Copy Assignment Operator =


  0: #include <iostream>
  1: using namespace std;
  2: #include <string.h>
  3: class MyString
  4: {
  5: private:
  6:    char* buffer;
  7:
  8: public:
  9:    MyString(const char* initialInput)
 10:    {
 11:       if(initialInput != NULL)
 12:       {
 13:          buffer = new char [strlen(initialInput) + 1];
 14:          strcpy(buffer, initialInput);
 15:      }
 16:       else
 17:          buffer = NULL;
 18:    }
 19:
 20:    // Copy assignment operator
 21:    MyString& operator= (const MyString& copySource)
 22:    {
 23:       if ((this != &copySource) && (copySource.buffer != NULL))
 24:       {
 25:          if (buffer != NULL)
 26:           delete[] buffer;
 27:
 28:          // ensure deep copy by first allocating own buffer
 29:          buffer = new char [strlen(copySource.buffer) + 1];
 30:
 31:          // copy from the source into local buffer
 32:          strcpy(buffer, copySource.buffer);
 33:       }
 34:
 35:      return *this;
 36:    }
 37:
 38:    operator const char*()
 39:    {
 40:       return buffer;
 41:    }
 42:
 43:    ~MyString()
 44:    {
 45:        delete[] buffer;
 46:    }
 47: };
 48:
 49: int main()
 50: {
 51:    MyString string1("Hello ");
 52:    MyString string2(" World");
 53:
 54:    cout << "Before assignment: " << endl;
 55:    cout << string1 << string2 << endl;
 56:    string2 = string1;
 57:    cout << "After assignment string2 = string1: " << endl;
 58:    cout << string1 << string2 << endl;
 59:
 60:    return 0;
 61: }


Output Image

Before assignment:
Hello  World
After assignment string2 = string1:
Hello Hello

Analysis Image

I have purposely omitted the copy constructor in this sample to reduce lines of code (but you should be inserting it when programming such a class; refer Listing 9.9 as a reference). The copy assignment operator is implemented in Lines 21 to 36. It is similar in function to a copy constructor and performs a starting check to ensure that the same object is not both the copy source and destination. After the checks return true, the copy assignment operator for MyString first deallocates its internal buffer before reallocating space for the text from the copy source and then uses strcpy() to copy, as shown in Line 14.


Note

Another subtle change in Listing 12.8 over Listing 9.9 is that you have replaced function GetString() by operator const char* as shown in Lines 38 to 41. This operator makes it even easier to use class MyString, as shown in Line 55, where one cout statement is used to display two instances of MyString.



Caution

When implementing a class that manages a dynamically allocated resource such as an array allocated using new, always ensure that you have implemented (or evaluated the implementation of) the copy constructor and the copy assignment operator in addition to the constructor and the destructor.

Unless you address the issue of resource ownership when an object of your class is copied, your class is incomplete and endangers the stability of the application when used.



Tip

To create a class that cannot be copied, declare the copy constructor and copy assignment operator as private. Declaration as private without implementation is sufficient for the compiler to throw error on all attempts at copying this class via passing to a function by value or assigning one instance into another.


Subscript Operator ([])

The operator that allow array-style [] access to a class is called subscript operator. The typical syntax of a subscript operator is:

return_type& operator [] (subscript_type& subscript);

So, when creating a class such as MyString that encapsulates a dynamic array class of characters in a char* buffer, a subscript operator makes it really easy to randomly access individual characters in the buffer:

class MyString
{
    // ... other class members
public:
    /*const*/ char& operator [] (int index) /*const*/
   {
        // return the char at position index in buffer
   }
};

The sample in Listing 12.9 demonstrates how the subscript operator ([]) helps the user in iterating through the characters contained in an instance of MyString using normal array semantics.

LISTING 12.9 Implementing Subscript Operator [] in class MyString to Allow Random Access to Characters Contained in MyString::buffer


  0: #include <iostream>
  1: #include <string>
  2: #include <string.h>
  3: using namespace std;
  4: class MyString
  5: {
  6: private:
  7:    char* buffer;
  8:
  9:    // private default constructor
 10:    MyString() {}
 11:
 12: public:
 13:    // constructor
 14:    MyString(const char* initialInput)
 15:    {
 16:       if(initialInput != NULL)
 17:       {
 18:          buffer = new char [strlen(initialInput) + 1];
 19:          strcpy(buffer, initialInput);
 20:       }
 21:       else
 22:          buffer = NULL;
 23:    }
 24:
 25:    // Copy constructor: insert from Listing 9.9 here
 26:    MyString(const MyString& copySource);
 27:
 28:    // Copy assignment operator: insert from Listing 12.8 here
 29:    MyString& operator= (const MyString& copySource);
 30:
 31:    const char& operator[] (int index) const
 32:    {
 33:       if (index < GetLength())
 34:          return buffer[index];
 35:    }
 36:
 37:    // Destructor
 38:    ~MyString()
 39:    {
 40:       if (buffer != NULL)
 41:          delete [] buffer;
 42:    }
 43:
 44:    int GetLength() const
 45:    {
 46:       return strlen(buffer);
 47:    }
 48:
 49:    operator const char*()
 50:    {
 51:       return buffer;
 52:    }
 53: };
 54:
 55: int main()
 56: {
 57:    cout << "Type a statement: ";
 58:    string strInput;
 59:    getline(cin, strInput);
 60:
 61:    MyString youSaid(strInput.c_str());
 62:
 63:    cout << "Using operator[] for displaying your input: " << endl;
 64:    for(int index = 0; index < youSaid.GetLength(); ++index)
 65:       cout << youSaid[index] << " ";
 66:    cout << endl;
 67:
 68:    cout << "Enter index 0 - " << youSaid.GetLength() - 1 << ": ";
 69:    int index = 0;
 70:    cin >> index;
 71:    cout << "Input character at zero-based position: " << index;
 72:    cout << " is: "<< youSaid[index] << endl;
 73:
 74:    return 0;
 75: }


Output Image

Type a statement: Hey subscript operators[] are fabulous
Using operator[] for displaying your input:
H e y   s u b s c r i p t   o p e r a t o r s [ ]   a r e   f a b u l o u s
Enter index 0 - 37: 2
Input character at zero-based position: 2 is: y

Analysis Image

This is just a fun program that takes a sentence you input, constructs a MyString using it, as shown in Line 61, and then uses a for loop to print the string character by character with the help of the subscript operator ([]) using an array-like syntax, as shown in Lines 64 and 65. The operator ([]) itself is defined in Lines 31 to 35 and supplies direct access to the character at the specified position after ensuring that the requested position is not beyond the end of the char* buffer.


Caution

Using keyword const is important even when programming operators. Note how Listing 12.9 has restricted the return value of subscript operator [] to const char&. The program works and compiles even without the const keywords, yet the reason you have it there is to avoid this code:

MyString sayHello("Hello World");
sayHello[2] = 'k'; //error: operator[] is const

By using const you are protecting internal member MyString::buffer from direct modifications from the outside via operator []. In addition to classifying the return value as const, you even have restricted the operator function type to const to ensure that it cannot modify the class’s member attributes.

In general, use the maximum possible const restriction to avoid unintentional data modifications and increase protection of the class’s member attributes.


When implementing subscript operators, you can improve on the version shown in Listing 12.9. That one is an implementation of a single subscript operator that works for both reading from and writing to the slots in the dynamic array.

You can, however, implement two subscript operators—one as a const function and the other as a non-const one:

char& operator [] (int index);  // use to write / change buffer at index
char& operator [] (int index) const; // used only for accessing char at index

The compiler will invoke the const function for read operations and the non-const version for operations that write into the MyString object. Thus, you can (if you want to) have separate functionalities in the two subscript operations. There are other binary operators (listed in Table 12.2) that can be redefined or overloaded, but that are not discussed further in this lesson. Their implementation, however, is similar to those that have already been discussed.

Other operators, such as the logical operators and the bitwise operators, need to be programmed if the purpose of the class would be enhanced by having them. Clearly, a calendar class such as Date does not necessarily need to implement logical operators, whereas a class that performs string and numeric functions might need them frequently.

Keep the objective of your class and its use in perspective when overloading operators or writing new ones.

Function Operator ()

The operator () that make objects behave like a function is called a function operator. They find application in the standard template library (STL) and are typically used in STL algorithms. Their usage can include making decisions; such function objects are typically called unary or binary predicates, depending on the number of operands they work on. Listing 12.10 analyzes a really simple function object so you can first understand what gives them such an intriguing name!

LISTING 12.10 A Function Object Created Using Operator ()


  1: #include <iostream>
  2: #include <string>
  3: using namespace std;
  4:
  5: class Display
  6: {
  7: public:
  8:     void operator () (string input) const
  9:     {
 10:       cout << input << endl;
 11:    }
 12: };
 13:
 14: int main ()
 15: {
 16:   Display displayFuncObj;
 17:
 18:   // equivalent to displayFuncObj.operator () ("Display this string! ");
 19:   displayFuncObj ("Display this string! ");
 20:
 21:   return 0;
 22: }


Output Image

Display this string!

Analysis Image

Lines 8 to 11 implement operator() that is then used inside the function main() at Line 19. Note how the compiler allows the use of object displayFuncObj as a function in Line 19 by implicitly converting what looks like a function call to a call to operator().

Hence, this operator is also called the function operator (), and the object of Display is also called a function object or functor. This topic is discussed exhaustively in Lesson 21, “Understanding Function Objects.

Move Constructor and Move Assignment Operator for High Performance Programming

The move constructor and the move assignment operators are performance optimization features that have become a part of the standard in C++11, ensuring that temporary values (rvalues that don’t exist beyond the statement) are not wastefully copied. This is particularly useful when handling a class that manages a dynamically allocated resource, such as a dynamic array class or a string class.

The Problem of Unwanted Copy Steps

Take a look at the addition operator+ as implemented in Listing 12.4. Notice that it actually creates a copy and returns it. If class MyString as demonstrated in Listing 12.9 supported the addition operator+, the following lines of code would be valid examples of easy string concatenation:

MyString Hello("Hello ");
MyString World("World");
MyString CPP(" of C++");
MyString sayHello(Hello + World + CPP);  // operator+, copy constructor
MyString sayHelloAgain ("overwrite this");
sayHelloAgain = Hello + World + CPP;  // operator+, copy constructor, copy
assignment operator=

This simple construct that makes concatenating three strings easy, uses the binary addition operator+:

MyString operator+ (const MyString& addThis)
{
   MyString newStr;

   if (addThis.buffer != NULL)
   {
      // copy into newStr
   }
   return newStr;  // return copy by value, invoke copy constructor
}

While making it easy to concatenate the strings, the addition operator+ can cause performance problems. The creation of sayHello requires the execution of the addition operator twice. Each execution of operator+ results in the creation of a temporary copy as a MyString is returned by value, thus causing the execution of the copy constructor. The copy constructor executes a deep copy—to a temporary value that does not exist after the expression. Thus, this expression results in temporary copies (rvalues, for the purists) that are not ever required after the statement and hence are a performance bottleneck forced by C++. Well, until recently at least.

This problem has now finally been resolved in C++11 in which the compiler specifically recognizes temporaries and uses move constructors and move assignment operators, where supplied by the programmer.

Declaring a Move Constructor and Move Assignment Operator

The syntax of the move constructor is as follows:

class Sample
{
private:
   Type* ptrResource;

public:
   Sample(Sample&& moveSource) // Move constructor, note &&
   {
      ptrResource = moveSource.ptrResource; // take ownership, start move
      moveSource.ptrResource = NULL;
   }

   Sample& operator= (Sample&& moveSource)//move assignment operator, note &&
   {
      if(this != &moveSource)
      {
         delete [] ptrResource;  // free own resource
         ptrResource = moveSource.ptrResource;  // take ownership, start move
         moveSource.ptrResource = NULL;  // free move source of ownership
      }
   }

   Sample(); // default constructor
   Sample(const Sample& copySource); // copy constructor
   Sample& operator= (const Sample& copySource); // copy assignment
};

Thus, the declaration of the move constructor and assignment operator are different from the regular copy constructor and copy assignment operator in that the input parameter is of type Sample&&. Additionally, as the input parameter is the move-source, it cannot be a const parameter as it is modified. Return values remain the same, as these are overloaded versions of the constructor and the assignment operator, respectively.

C++11 compliant compilers ensure that for rvalue temporaries the move constructor is used instead of the copy constructor and the move assignment operator is invoked instead of the copy assignment operator. In your implementation of these two, you ensure that instead of copying, you are simply moving the resource from the source to the destination. Listing 12.11 demonstrates the effectiveness of these two recent additions in optimizing class MyString.

LISTING 12.11 class MyString with Move Constructor and Move Assignment Operator in Addition to Copy Constructor and Copy Assignment Operator


   0: #include <iostream>
   1: #include <string.h>
   2: using namespace std;
   3: class MyString
   4: {
   5: private:
   6:    char* buffer;
   7:
   8:    MyString(): buffer(NULL) // private default constructor
   9:    {
  10:       cout << "Default constructor called" << endl;
  11:    }
  12:
  13: public:
  14:    MyString(const char* initialInput) // constructor
  15:    {
  16:       cout << "Constructor called for: " << initialInput << endl;
  17:       if(initialInput != NULL)
  18:       {
  19:          buffer = new char [strlen(initialInput) + 1];
  20:          strcpy(buffer, initialInput);
  21:       }
  22:       else
  23:          buffer = NULL;
  24:    }
  25:
  26:    MyString(MyString&& moveSrc) // move constructor
  27:    {
  28:       cout << "Move constructor moves: " << moveSrc.buffer << endl;
  29:       if(moveSrc.buffer != NULL)
  30:       {
  31:          buffer = moveSrc.buffer; // take ownership i.e.  'move'
  32:          moveSrc.buffer = NULL;   // free move source
  33:       }
  34:     }
  35:
  36:    MyString& operator= (MyString&& moveSrc) // move assignment op.
  37:    {
  38:       cout << "Move assignment op. moves: " << moveSrc.buffer << endl;
  39:       if((moveSrc.buffer != NULL) && (this != &moveSrc))
  40:       {
  41:          delete[] buffer; // release own buffer
  42:
  43:          buffer = moveSrc.buffer; // take ownership i.e.  'move'
  44:          moveSrc.buffer = NULL;   // free move source
  45:       }
  46:
  47:       return *this;
  48:    }
  49:
  50:    MyString(const MyString& copySrc) // copy constructor
  51:    {
  52:       cout << "Copy constructor copies: " << copySrc.buffer << endl;
  53:       if (copySrc.buffer != NULL)
  54:       {
  55:          buffer = new char[strlen(copySrc.buffer) + 1];
  56:          strcpy(buffer, copySrc.buffer);
  57:       }
  58:       else
  59:          buffer = NULL;
  60:    }
  61:
  62:    MyString& operator= (const MyString& copySrc) // Copy assignment op.
  63:    {
  64:       cout << "Copy assignment op. copies: " << copySrc.buffer << endl;
  65:       if ((this != &copySrc) && (copySrc.buffer != NULL))
  66:       {
  67:          if (buffer != NULL)
  68:            delete[] buffer;
  69:
  70:         buffer = new char[strlen(copySrc.buffer) + 1];
  71:         strcpy(buffer, copySrc.buffer);
  72:      }
  73:
  74:      return *this;
  75:   }
  76:
  77:   ~MyString() // destructor
  78:   {
  79:      if (buffer != NULL)
  80:         delete[] buffer;
  81:    }
  82:
  83:    int GetLength()
  84:   {
  85:       return strlen(buffer);
  86:    }
  87:
  88:    operator const char*()
  89:    {
  90:       return buffer;
  91:   }
  92:
  93:    MyString operator+ (const MyString& addThis)
  94:   {
  95:       cout << "operator+ called: " << endl;
  96:       MyString newStr;
  97:
  98:       if (addThis.buffer != NULL)
  99:       {
 100:          newStr.buffer = new char[GetLength()+strlen(addThis.buffer)+1];
 101:          strcpy(newStr.buffer, buffer);
 102:          strcat(newStr.buffer, addThis.buffer);
 103:       }
 104:
 105:      return newStr;
 106:    }
 107: };
 108:
 109: int main()
 110: {
 111:    MyString Hello("Hello ");
 112:    MyString World("World");
 113:    MyString CPP(" of C++");
 114:
 115:    MyString sayHelloAgain ("overwrite this");
 116:    sayHelloAgain = Hello + World + CPP;
 117:
 118:    return 0;
 119: }


Output Image

Output without the move constructor and move assignment operator (by commenting out Lines 26 to 48):

Constructor called for: Hello
Constructor called for: World
Constructor called for:  of C++
Constructor called for: overwrite this
operator+ called:
Default constructor called
Copy constructor copies: Hello World
operator+ called:
Default constructor called
Copy constructor copies: Hello World of C++
Copy assignment op. copies: Hello World of C++

Output with the move constructor and move assignment operator enabled:

Constructor called for: Hello
Constructor called for: World
Constructor called for:  of C++
Constructor called for: overwrite this
operator+ called:
Default constructor called
Move constructor moves: Hello World
operator+ called:
Default constructor called
Move constructor moves: Hello World of C++
Move assignment op. moves: Hello World of C++

Analysis Image

This might be a really long code sample, but most of it has already been demonstrated in previous examples and lessons. The most important part of this listing is in Lines 26 to 48 that implement the move constructor and the move assignment operator, respectively. Parts of the output that have been influenced by this new addition to C++11 has been marked in bold. Note how the output changes drastically when compared against the same class without these two entities. If you look at the implementation of the move constructor and the move assignment operator again, you see that the move semantic is essentially implemented by taking ownership of the resources from the move source moveSrc as shown in Line 31 in the move constructor and Line 43 in the move assignment operator. This is immediately followed by assigning NULL to the move source pointer as shown in Lines 32 and 44. This assignment to NULL ensures that the destructor of the instance that is the move source essentially does no memory deallocation via delete in Line 80 as the ownership has been moved to the destination object. Note that in the absence of the move constructor, the copy constructor is called that does a deep copy of the pointed string. Thus, the move constructor has saved a good amount of processing time in reducing unwanted memory allocations and copy steps.

Programming the move constructor and the move assignment operator is completely optional. Unlike the copy constructor and the copy assignment operator, the compiler does not add a default implementation for you.

Use this feature to optimize the functioning of classes that point to dynamically allocated resources that would otherwise be deep copied even in scenarios where they’re only required temporarily.

User Defined Literals

Literal constants were introduced in Lesson 3, “Using Variables, Declaring Constants.” Here are some examples of a few:

int bankBalance = 10000;
double pi = 3.14;
char firstAlphabet = ‘a’;
const char* sayHello = "Hello!";

In the preceding code, 10000, 3.14, ‘a’, and "Hello!" are all literal constants! C++11 extended the standard’s support of literals by allowing you to define your own literals. For instance, if you were working on a scientific application that deals with thermodynamics, you may want all your temperatures to be stored and operated using a scale called Kelvin. You may now declare all your temperatures using a syntax similar to the following:

Temperature k1 = 32.15_F;
Temperature k2 = 0.0_C;

Using literals _F and _C that you have defined, you have made your application a lot simpler to read and therefore maintain.

To define your own literal, you define operator "" like this:

ReturnType operator "" YourLiteral(ValueType value)
{
   // conversion code here
}


Note

Depending on the nature of the user defined literal, the ValueType parameter would be restricted to one of the following:

unsigned long long int for integral literal

long double for floating point literal

char, wchar_t, char16_t, and char32_t for character literal

const char* for raw string literal

const char* together with size_t for string literal

const wchar_t* together with size_t for string literal

const char16_t* together with size_t for string literal

const char32_t* together with size_t for string literal


Listing 12.12 demonstrates a user defined literal that converts types.

LISTING 12.12 Conversion from Fahrenheit and Centigrade to the Kelvin Scale


  0: #include <iostream>
  1: using namespace std;
  2:
  3: struct Temperature
  4: {
  5:    double Kelvin;
  6:    Temperature(long double kelvin) : Kelvin(kelvin) {}
  7: };
  8:
  9: Temperature operator"" _C(long double celcius)
 10: {
 11:    return Temperature(celcius + 273);
 12: }
 13:
 14: Temperature operator "" _F(long double fahrenheit)
 15: {
 16:    return Temperature((fahrenheit + 459.67) * 5 / 9);
 17: }
 18:
 19: int main()
 20: {
 21:    Temperature k1 = 31.73_F;
 22:    Temperature k2 = 0.0_C;
 23:
 24:    cout << "k1 is " << k1.Kelvin << " Kelvin" << endl;
 25:    cout << "k2 is " << k2.Kelvin << " Kelvin" << endl;
 26:
 27:    return 0;
 28: }


Output Image

k1 is 273 Kelvin
k2 is 273 Kelvin

Analysis Image

Lines 21 and 22 in the sample above initialize two instances of Temperature, one using a user defined literal _F to declare an initial value in Fahrenheit and the other using a user defined literal to declare an initial value in Celcius (also called Centigrade). The two literals are defined in Lines 9–17, and do the work of converting the respective units into Kelvin and returning an instance of Temperature. Note that k2 has intentionally been initialized to 0.0_C and not to 0_C, because the literal _C has been defined (and is required) to take a long double as input value and 0 would’ve interpreted as an integer.

Operators That Cannot Be Overloaded

With all the flexibility that C++ gives you in customizing the behavior of the operators and making your classes easy to use, it still keeps some cards to itself by not allowing you to change or alter the behavior of some operators that are expected to perform consistently. The operators that cannot be redefined are shown in Table 12.3.

Image

TABLE 12.3 Operators That CANNOT Be Overloaded or Redefined

Summary

You learned how programming operators can make a significant difference to the ease with which your class can be consumed. When programming a class that manages a resource, for example a dynamic array or a string, you need to supply a copy constructor and copy assignment operator for a minimum, in addition to a destructor. A utility class that manages a dynamic array can do very well with a move constructor and a move assignment operator that ensures that the contained resource is not deep-copied for temporary objects. Last but not least, you learned that operators such as ., .*, ::, ?:, and sizeof cannot be redefined.

Q&A

Q My class encapsulates a dynamic array of integers. What functions and operators should I implement for a minimum?

A When programming such a class, you need to clearly define the behavior in the scenario where an instance is being copied directly into another via assignment or copied indirectly by being passed to a function by value. You typically implement the copy constructor, copy assignment operator, and the destructor. You also implement the move constructor and move assignment operator if you want to tweak the performance of this class in certain cases. To enable an array-like access to elements stored inside an instance of the class, you would want to overload the subscript operator[].

Q I have an instance object of a class. I want to support this syntax: cout << object;.What operator do I need to implement?

A You need to implement a conversion operator that allows your class to be interpreted as a type that std::cout can handle upfront. One way is to define operator char*() as you also did in Listing 12.2.

Q I want to create my own smart pointer class. What functions and operators do I need to implement for a minimum?

A A smart pointer needs to supply the ability of being used as a normal pointer as in *pSmartPtr or pSmartPtr->Func(). To enable this you implement operator (*) and operator (->). In addition, for it to be smart, you also take care of automatic resource release/returns by programming the destructor accordingly, and you would clearly define how copy or assignment works by implementing the copy constructor and copy assignment operator or by prohibiting it by declaring these two as private.

Workshop

The Workshop contains quiz questions to help solidify your understanding of the material covered and exercises to provide you with experience in using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix E, and be certain you understand the answers before going to the next lesson.

Quiz

1. Can my subscript operator [] return const and non-const variants of return types?

const Type& operator[](int index);
Type& operator[](int index); // is this OK?

2. Would you ever declare the copy constructor or copy assignment operator as private?

3. Would it make sense to define a move constructor and move assignment operator for your class Date?

Exercises

1. Program a conversion operator for class Date that converts the date it holds into a unique integer.

2. Program a move constructor and move assignment operator for class DynIntegers that encapsulates a dynamically allocated array in the form of private member int*.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.242.62