11. Introduction to Object Oriented Programming

1. Youtube videos

These notes work best together with 5 youtube videos about object oriented programming. The videos are similar to these notes, but have more details. This page contains the code that is developed in the videos. Here is the link to the 5 videos about object oriented programming.

2. Classes and objects

The simplest data structures such as long, double, int can store numerical values. Programmers can expand the language by making their own customized data structures. We already had some experience with custom data structures. The nodes of linked lists and the nodes of stacks were examples of data structures that we had to make ourselves.

The custom data structures are called classes. They have to be declared before they are used. The classes are declared near the top of the program.

We will make the class CCAccount that represents credit card account. We are going to imagine that we are writing a software for a very influential bank. In our first draft of the program, the class will be very simple. It will have only two fields: cName, which represents the name of the customer, and balance which represents the balance of the account. Positive balance means that the customer owes money to the bank.

Here is our first program

#include<iostream>
class CCAccount{
public:
  std::string cName;
  double balance;
};
int main(){
  CCAccount x;
  x.cName="Watson";
  x.balance=55.21;
  std::cout<<"The customer "<<x.cName<<" owes ";
  std::cout<<x.balance<<" dollars.\n";
  return 0;
}

Terminology: CCAccount is called class. In function main() we introduced the variable x whose type is CCAccount. This variable x is called object. The data fields cName and balance are called attributes.

Classes can have specialized functions that can be called in a new, convenient, way. You have never seen such function calls before. We will create a feature called printStatement that will be invoked with x.printStatement(); First, we must modify the class declaration from the one we wrote before.

class CCAccount{
public:
  std::string cName;
  double balance;
  void printStatement();
};

This new declaration is making an announcement that there will be a function called printStatement that won't have any parameters. After this class declaration, we will create the implementation of the function

void CCAccount::printStatement(){
  std::cout<<"Customer name: "<<cName<<"\n";
  std::cout<<"Balance: "<<balance<<"\n";
}

After these additions to our code, we are able to use the instruction x.printStatement(); The new, updated, program is given below.

#include<iostream>
class CCAccount{
public:
  std::string cName;
  double balance;
  void printStatement();
};
void CCAccount::printStatement(){
  std::cout<<"Customer name: "<<cName<<"\n";
  std::cout<<"Balance: "<<balance<<"\n";
}
int main(){
  CCAccount x;
  x.cName="Watson";
  x.balance=55.21;
  x.printStatement();
  return 0;
}

The function printStatement is called a method. Methods are functions associated with classes. Methods and attributes are called members of the class.

Problem 1. What are classes, objects, members, attributes, and methods?

Let us create one more method: addCharge. This method will have one parameter: the number x. The number x represents the money that should be added to the balance. In real world, this occurs when the customer goes shopping and uses the credit card for buying things that aren't free.

In the declaration section, we need to add the following line

void addCharge(double );

In the implementation section, which is below the declaration, we need to add the following code:

void CCAccount::addCharge(double x){
  balance+=x;
}

Notice how in declaration we do not write the name of our parameter!

Now, in the main function, we can add instructions like this: x.addCharge(15.55);

3. Public and private

Some programmers have friends. If you don't, then imagine you have one for the purposes of this section. Assume that you are working with your friend on making the software that uses CCAccount and that you want to split the software implementation. One wants to focus on building the class CCAccount and its methods. We will call this person C Programmer. The other wants to work on making the perfect main() function that will use the class and the methods. This one will be called M Programmer.

C Programmer should prevent M Programmer from messing up the content of the attribute balance. The C Programmer already made the method addCharge and C Programmer doesn't want the attribute balance to be changed in any way other than through this method.

This is the solution. The attribute balance will be made private. The class declaration will change into

class CCAccount{
private: 
  double balance;
public:
  std::string cName; 
  void printStatement();
};

When the declaration is modified to look like the one above, then the attribute balance cannot be accessed outside of the class methods. In particular, the attribute balance cannot be accessed from the main() with the instructions x.balance. Every object of the class CCAccount will still have the attribute balance, but only C Programmer is now permitted to read and write into this attribute. If M Programmer tries to access balance, then the compiler will treat that as an accident and refuse to make the binary. Instead, the compiler will give some angry error message. Very soon you'll start writing your own programs and you'll see these messages. Every programmer got them at one point or another. That's what I've heard.

We are still not ready to present our modified code with the correct use of private and public, you'll need to read the next section (called 4. Constructor) to see why.

4. Constructor

By now we have decided to put balance into the private section. We now must remove all the instructions from main() that are accessing x.balance. Once we do such removal, there is one problem: the initial value balance may contain garbage content. The instruction CCAccount x; asks the computer to give sufficient memory for one CCAccount. The computer will give the memory. It will have sufficient space for x.cName and x.balance. The space will be there, but it may have some remaining garbage that happened to be left by previous process that was using the memory.

Luckily, we are allowed to make a method that is called every time a new object is created. Such method is called constructor. The convention in C++ is that the name of constructor is the same as the name of the class. The updated class declaration is

class CCAccount{
private: 
  double balance;
public:
  std::string cName; 
  CCAccount();
  void printStatement();
  void addCharge(double );
};

In the implementation section, we should add

CCAccount::CCAccount(){
  balance=0.0;
  cName="NoNameYet";
}

We are allowed to have multiple constructors. We can make another constructor that initializes the name. The updated program becomes

#include<iostream>
class CCAccount{
private: 
  double balance;
public:
  std::string cName; 
  CCAccount();
  CCAccount(std::string);
  void printStatement();
  void addCharge(double );
}; 
CCAccount::CCAccount(){
  balance=0.0;
  cName="NoNameYet";
}
CCAccount::CCAccount(std::string n){
  balance=0.0;
  cName=n;
}
void CCAccount::addCharge(double x){
  balance+=x;
}
void CCAccount::printStatement(){
  std::cout<<"Customer name: "<<cName<<"\n";
  std::cout<<"Balance: "<<balance<<"\n";
}
int main(){
  CCAccount x;
  x.cName="Watson";
  x.addCharge(55.21);
  x.addCharge(15.55);
  CCAccount y("EvilCommander");
  y.addCharge(50.1); y.addCharge(60.15);
  x.printStatement();
  y.printStatement();
  return 0;
}

5. Class and struct

In C++ we can use class and struct for class declarations. The main difference between the two is the default access type in the case when the keywords public and private were not provided by the programmer: In the case of struct, the default is public. In the case of class, the default is private. However, the words public and private should always be provided. There is no reason to omit them. To summarize this, let us put this as a problem

Problem 2. What is the difference between class and struct in C++?

6. Destructor

Please review the chapter 6. Stacks before reading this section.

We will improve the class CCAccount. We will add the stack of all transactions that the user has made. Once a new charge is added, it will be pushed to the top of the stack. We must declare the nodes of the stack, and the few fundamental stack functions such as push and pop.

We will add a private attribute called aTT that will be the address of the top of the transactions. Then we can improve the method addCharge. We will include the instruction that pushes the newest charge to the top of the stack. The method printStatement will also be updated. It will print all the transactions.

We will also have to prevent the memory leak. This will be achieved by implementing the destructor. The destructor is the method that is called when the program abandons the object. Such abandoning occurs when the object goes out of scope. Alternatively, if the object was created with the command new, the command delete calls the destructor. The name of the destructor starts with the character ~ and is followed by the name of the class. Our improved class with the stack and the destructor is given below.

#include<iostream>
struct SN{// Stack Node
 public:
  double content;
  SN* aNext;
 };
 SN* push(SN* oH, double x){
  SN* nN;
  nN=new SN;
  nN->content=x;
  nN->aNext=oH;
  return nN;
 }
 SN* pop(SN* oH){
  if(oH==nullptr){
    return nullptr;
  }
  SN* nextNode;
  nextNode=oH->aNext;
  delete oH;
  return nextNode;
 }
 double readTop(SN* aH){
  if(aH==nullptr){
    return 0.0;
  }
  return aH->content;
 }
 int isEmpty(SN* aH){
  if(aH==nullptr){
    return 1;
  }
  return 0;
 }
 void deleteStack(SN* aH){
  while(aH!=nullptr){
    aH=pop(aH);
  }
 }
class CCAccount{
private: 
  double balance;
  SN* aTT;
public:
  std::string cName; 
  CCAccount();
  CCAccount(std::string);
  void printStatement();
  void addCharge(double );
  ~CCAccount();
}; 
CCAccount::CCAccount(){
  aTT=nullptr;
  balance=0.0;
  cName="NoNameYet";
}
CCAccount::CCAccount(std::string n){
  aTT=nullptr;
  balance=0.0;
  cName=n;
}
CCAccount::~CCAccount(){
  while(aTT!=nullptr){
    aTT=pop(aTT);
  }
}
void CCAccount::addCharge(double x){
  balance+=x;
  aTT=push(aTT,x);
}
void CCAccount::printStatement(){
  std::cout<<"Customer name: "<<cName<<"\n";
  std::cout<<"Balance: "<<balance<<"\n";
  std::cout<<"Transactions: ";
  SN* runner=aTT;
  while(runner!=nullptr){
    std::cout<<runner->content<<" ";
    runner=runner->aNext;
  }
  std::cout<<"\n";
}
int main(){
  CCAccount x;
  x.cName="Watson";
  x.addCharge(55.21);
  x.addCharge(15.55);
  CCAccount y("EvilCommander");
  y.addCharge(50.1); y.addCharge(60.15);
  x.printStatement();
  y.printStatement();
  return 0;
}

7. Keyword const

To improve the communication between programmers, the language C++ allows us to use the keyword const in front of variables. When const is placed in front of a parameter of a function, then it is a promise that the function will not change the parameter. If it does, the compiler will report an error and refuse to make the executable binary.

At first glance the keyword const offers no benefits to the programmers, just the trouble: it can cause the compilation to be terminated and it does not introduce new features to the final code. The benefit is readability of the code. When someone reads the word const in front of the variable that is passed by reference, they get the information that the function will not change the variable.

When the keyword const is used after the declaration of the method, then the creator of the method is making a promise that the method will not change any of the attributes. In other words, the method is "accessor" and not a "mutator". In fact a little stronger promise is made: the method will not only leave the attributes unchanged, it will also not call any other methods unless they are const. When making complex projects that involve multiple teams of programmers, individual contributors are often required to use the keyword const whenever possible.

In our next version of the code we will add this keyword as necessary.

8. Copy constructor and copy assignment

There is a serious weakness in our current implementation of the class CCAccount. Let us keep everything from our latest version of the code, except for the main function. We will replace the main function with the following

int main(){
  CCAccount x("Watson");
  x.addCharge(10.0);x.addCharge(20.0);x.addCharge(30);
  {
    CCAccount y; 
    y=x;
    y.cName="EvilCommander";
    y.printStatement();
  }
  x.printStatement();
  return 0;
}

The resulting C++ code can be compiled and a binary can be successfully created. The execution of the binary will result in a crash. The call y.printStatement(); will execute successfully, but the call x.printStatement(); will not. The function main() contains an inner block of code in which y is declared. The object y goes out of scope after the program is done with this inner block. In this inner block we have the instruction y=x;, which makes the copies of the attributes balance, aTT, and cName. The attribute aTT is a pointer. It contains the address of the top of the stack. The pointers x.aTT and y.aTT contain the same address. The destructor for y de-allocates the stack. However, x.aTT still contains the address of the stack, which is now de-allocated. When the method x.printStatement() is called, it makes an illegal access to the de-allocated elements of the stack.

This problem is solved with a custom method that will be executed when the instruction y=x; is called. The language C++ made it possible to create such method. It is called copy assignment. The instruction y=x; is called assignment. The name of the method is operator=. The instruction y=x; is equivalent to y.operator=(x); Since we did not implement our version of the assignment operator operator=, the default version was executed. The default version just copies the attributes. To make an improvement, we add the line void operator=(const CCAccount& ); in the declaration. We add the function copyStack before the declaration of CCAccount. The function copyStack and the implementation of the assignment operator are given below.

SN* copyStack(SN* aT){
  if(aT==nullptr){return nullptr;}
  SN* aNew=new SN;
  aNew->content=aT->content;
  aNew->aNext=copyStack(aT->aNext);
  return aNew;
}
void CCAccount::operator=(const CCAccount& b){
  cName=b.cName; balance=b.balance;
  deleteStack(aTT);
  aTT=copyStack(b.aTT);
}

Our copy assignment is still not free from defects. It is vulnerable to the self-assignment bug. If for some reason the instruction x=x; is executed, then the argument of the assignment operator would be the same as the object to which it is applied. The stack would be destroyed before it was copied. To protect from self-assignment bug, we will look the address of the parameter (which is &b in our case). We will compare the address with the address of the object whose method is called. This address is stored in the special variable called this. Every class has the attribute this, which is a pointer. It contains the address of the object. The improved assignment operator is

void CCAccount::operator=(const CCAccount& b){
  if(&b!=this){
    cName=b.cName; balance=b.balance;
    deleteStack(aTT);
    aTT=copyStack(b.aTT);
  }
}

Our class implementation also needs the copy constructor. The copy assignment is not enough. Our newest function main() had the instructions CCAccount y; y=x; that were calling the copy assignment. However, if we replace these two instructions with the single instruction CCAccount y=x;, then the copy assignment won't be called. The instruction CCAccount y=x; is not equivalent to CCAccount y; y=x;. The instruction CCAccount y=x; is equivalent to CCAccount y(x);, which calls the copy constructor, i.e. the constructor whose declaration is

CCAccount(const CCAccount& );

Since we did not create such copy constructor, the compiler will execute a default version, that is just as bad as the default copy assignment. The compiler's version of the copy constructor makes copy of the attributes. To improve the program, we add the line CCAccount (const CCAccount& ); in the declaration and the following code in the implementation

CCAccount::CCAccount(const CCAccount& b){
  cName=b.cName; balance=b.balance;
  aTT=copyStack(b.aTT);
}

With copy constructor, we don't have to worry about self-assignment. Here is the improved code that includes the copy constructor and copy assignment.

#include<iostream>
struct SN{// Stack Node
 public:
  double content;
  SN* aNext;
};
SN* push(SN* oH, double x){
  SN* nN;
  nN=new SN;
  nN->content=x;
  nN->aNext=oH;
  return nN;
}
SN* pop(SN* oH){
  if(oH==nullptr){
    return nullptr;
  }
  SN* nextNode;
  nextNode=oH->aNext;
  delete oH;
  return nextNode;
} 
void deleteStack(SN* aH){
  while(aH!=nullptr){
    aH=pop(aH);
  }
}
SN* copyStack(SN* aT){
  if(aT==nullptr){return nullptr;}
  SN* aNew=new SN;
  aNew->content=aT->content;
  aNew->aNext=copyStack(aT->aNext);
  return aNew;
}
class CCAccount{
private: 
  double balance;
  SN* aTT;
public:
  std::string cName; 
  CCAccount();
  CCAccount(const std::string &);
  CCAccount(const CCAccount& );
  void operator=(const CCAccount& );
  void printStatement() const;
  void addCharge(double );
  ~CCAccount();
}; 
CCAccount::CCAccount(){
  aTT=nullptr;
  balance=0.0;
  cName="NoNameYet";
}
CCAccount::CCAccount(const std::string& n){
  aTT=nullptr;
  balance=0.0;
  cName=n;
}
CCAccount::CCAccount(const CCAccount& b){
  cName=b.cName; balance=b.balance;
  aTT=copyStack(b.aTT);
}
void CCAccount::operator=(const CCAccount& b){
  if(&b!=this){
    cName=b.cName; balance=b.balance;
    deleteStack(aTT);
    aTT=copyStack(b.aTT);
  }
}
CCAccount::~CCAccount(){
  while(aTT!=nullptr){
    aTT=pop(aTT);
  }
}
void CCAccount::addCharge(double x){
  balance+=x;
  aTT=push(aTT,x);
}
void CCAccount::printStatement() const{
  std::cout<<"Customer name: "<<cName<<"\n";
  std::cout<<"Balance: "<<balance<<"\n";
  std::cout<<"Transactions: ";
  SN* runner=aTT;
  while(runner!=nullptr){
    std::cout<<runner->content<<" ";
    runner=runner->aNext;
  }
  std::cout<<"\n";
}
int main(){
  CCAccount x("Watson");
  x.addCharge(10.0);x.addCharge(20.0);x.addCharge(30);
  {
    CCAccount y;
    y=x;
    y.cName="EvilCommander";
    y.printStatement();
  }
  x.printStatement();
  return 0;
}

9. Move constructor and move assignment

We will now analyze the functions that return objects. We will learn that the return instruction consists of five steps. The default behavior of return statement must provide the safe handling for all classes. We will see how this default behavior results in inefficient and slow code in some situations where classes have copy assignment and copy constructor that are copying large data structures. Then we will learn how to use move constructor and move assignment to improve the efficiency of the code.

We illustrate this by first creating the function makeFunnyAccount. We will place it just above the main() and modify the main() to include an instruction that calls this newly created makeFunnyAccount

CCAccount makeFunnyAccount(CCAccount x, CCAccount& y, CCAccount z){
  CCAccount funny;
  funny.cName=x.cName+y.cName+z.cName;
  funny.addCharge(100.0);funny.addCharge(50.0);
  return funny;
}
int main(){
  CCAccount p; CCAccount q; CCAccount r; 
  p.cName="P"; q.cName="Q"; r.cName="R";
  CCAccount w;
  w=makeFunnyAccount(p,q,r);
  w.printStatement();
  return 0;
}

There are no big surprises when the code from above is compiled and executed. The object w of of type CCAccount is created by the function. Its stack has two nodes with contents 50.0 and 100.0. The name w.cName is PQR.

We will now modify our constructors, destructor, and copy assignment operator. For each of them we will add an instruction that prints a message on cout. Then we will run the new code and look at the printout. The modified implementations of constructors, destructor, and the copy assignment are given below:

CCAccount::CCAccount(){
  aTT=nullptr;
  balance=0.0;
  cName="NoNameYet";
  std::cout<<"Constructor (default)\n";
}
CCAccount::CCAccount(const std::string& n){
  aTT=nullptr;
  balance=0.0;
  cName=n;
  std::cout<<"Constructor (with added name)\n";
}
CCAccount::CCAccount(const CCAccount& b){
  cName=b.cName; balance=b.balance;
  aTT=copyStack(b.aTT);
  std::cout<<"Copy constructor\n";
}
void CCAccount::operator=(const CCAccount& b){
  if(&b!=this){
    cName=b.cName; balance=b.balance;
    deleteStack(aTT);
    aTT=copyStack(b.aTT);
  }
  std::cout<<"Copy assignment\n";
}
CCAccount::~CCAccount(){
  while(aTT!=nullptr){
    aTT=pop(aTT);
  }
  std::cout<<"Destructor for "<<cName<<"\n";
} 

Add the compiler flag. Make sure you add the compiler flag -fno-elide-constructors. The command that calls the compiler should look like this:

c++ source.cpp -o binary -std=c++11 -fno-elide-constructors

Remark. The above flag will prevent the compiler from optimizing the binary. Some of the optimizations can remove educational experience that we must have at this moment. The truth is that our code is fairly simple and current compilers are very advanced. When you give the simplest of codes to an advanced compiler, then the compiler will figure out how to skip some of the five steps in the return instruction. In general, the compiler may not be always able to figure out how to do such a thing. Our code is simple, so compiler can be a hero. However, we must learn about move constructor and move assignment and then we will be ready to write complex software that does not assume that the compilers are smarter than us.

Here is the printout when the code is executed

01 Constructor (default)
02 Constructor (default)
03 Constructor (default)
04 Constructor (default)
05 Copy constructor
06 Copy constructor
07 Constructor (default)
08 Copy constructor
09 Destructor for PQR
10 Copy assignment
11 Destructor for PQR
12 Destructor for R
13 Destructor for P
14 Customer name: PQR
15 Balance: 150
16 Transactions: 50 100 
17 Destructor for PQR
18 Destructor for R
19 Destructor for Q
20 Destructor for P 

We have enumerated the 20 lines of the output. If you did not get these 20 lines, then you did not properly invoke the flag -fno-elide-constructors.

The lines 1-4 of the printout are due to the creation of the objects p, q, r, and w.

The function makeFunnyAccount receives x and z by value. It receives y by reference. The objects x and z are created by the copy constructor. The name y is a reference. It's a nickname for q from main() and not a new object. Therefore, the lines 5 and 6 are printed by copy constructors for x and z. The line 7 is printed because the object funny is created and its default constructor prints the corresponding message.

The remainder of the function is silent until the return statement. The return statement consists of the five steps that we will describe and analyze. The return statement is responsible for the printout lines 8-13. The five steps of the return statement are

  • Step 1. Creation of temporary object. The line 8 was printed.
  • Step 2. Destruction of local objects. In our case that's only the object funny. The line 9 was printed.
  • Step 3. Assignment to calling object (in this case that's w in main()) from temporary object. The line 10 was printed.
  • Step 4. Destruction of the temporary object. The line 11 was printed.
  • Step 5. Destruction of function parameters that were passed by value. The lines 12 and 13 were printed.

The remaining lines, 14-20 are easy to follow. The lines 14-16 are due to the instruction w.printStatement();. The lines 17-20 are caused by the destructors for w, r, q, and p that were called in the opposite order from the order in which the objects were created.

We can spot some inefficiencies. Two obvious inefficiencies were the passing of x and z by value, instead of passing by reference. That is something that we should keep in mind in our future programming.

Important lesson. Whenever possible, the function parameters should be passed by reference.

The previous lesson is easy to learn. Now, it is time to point to some more subtle inefficiencies. The temporary object was created in Step 1 using copy constructor. The stack was copied from funny to temporary object. Then, the object funny and its stack were immediately destroyed in Step 2. Then, in Step 3, the stack from the temporary object was copied to w from main() just nanoseconds before the temporary object and its stack were destroyed in Step 5.

C++ offers some significant improvements from the version C++11. They offer move constructor and move assignment. We are allowed to make a custom move constructor, and if we do so, then this custom move constructor will be called instead of the copy constructor in Step 1. We are also allowed to create a custom move assignment that will be called instead of the copy assignment in Step 3. Move constructor and move assignment are introduced with the following lines in the class declaration

CCAccount(CCAccount&& );
void operator=(CCAccount&& );

Their implementation of the move constructor is

CCAccount::CCAccount(CCAccount&& b){
  cName=b.cName;
  balance=b.balance;
  aTT=b.aTT;
  b.aTT=nullptr;
  std::cout<<"Move constructor\n";
}

Observe how we have avoided copying the stack. We just copied its address. However, we had to do the assignment b.aTT=nullptr; because the move constructor will have a temporary object for an argument. That object will soon be destroyed. We don't want its destructor to destroy the stack.

The two commands aTT=b.aTT; b.aTT=nullptr; when implemented in this order are called moving or resources or stealing of resources.

The implementation of the move assignment is given below.

CCAccount(CCAccount&& );
void operator=(CCAccount&& );

Their implementation of the move constructor is

void CCAccount::operator=(CCAccount&& b){
  if(&b!=this){
    cName=b.cName;
    balance=b.balance;
    deleteStack(aTT);
    aTT=b.aTT;
    b.aTT=nullptr;
  }
  std::cout<<"Move assignment\n";
}

With this improved code, the printout will have the lines 8 and 10 changed into 08 Move constructor and 10 Move assignment.

Notice that if in the main function, we changed the two instructions CCAccount w; w=makeFunnyAccount(p,q,r); into the single instruction CCAccount w=makeFunnyAccount(p,q,r); or, its equivalent, CCAccount w(makeFunnyAccount(p,q,r)); then line 10 will print the message Move constructor instead of Move assignment. The Step 3 in this case would call move constructor.

We can now formally write the 5 steps of the return statement.

Five steps of the return statement

  • Step 1. Creation of temporary object using move constructor;
  • Step 2. Destruction of local objects;
  • Step 3. Assignment to (or construction of) calling object from temporary object using move assignment (or move constructor);
  • Step 4. Destruction of the temporary object;
  • Step 5. Destruction of function parameters that were passed by value.

If move constructor was not provided by the programmer, then the copy constructor would be called instead. If move assignment was not provided, then the copy assignment would be called.

10. Assignment chaining

The built-in-types, such as long or double allow the assignment chaining. Let us look at the following code.

long x; long y; long z;
z=10;
x=y=z;

The instruction x=y=z; is assignment chaining that assigns the value 10 to all three of x, y, and z.

The chaining is possible because the assignment operator returns the reference. In the first step of x=y=z;, the assignment y=z; is performed. This assignment returns the reference to y. Such reference is then the input of the assignment applied to x.

In general, assignment chaining is discouraged. The code is considered more readable and elegant if the two instructions y=z; x=y; are used instead of a single on x=y=z;

Even though chaining is discouraged, we can make our classes consistent with built-in types by supporting the assignment chaining. If we want to do this, we need modify the assignment operators. They shouldn't be of type void. In the case of the class CCAccount, the operators should return CCAccount&. Their return instruction should be return *this;.

The updated code is given below.

#include<iostream>
struct SN{// Stack Node
public:
    double content;
    SN* aNext;
};
SN* push(SN* oH, double x){
    SN* nN;
    nN=new SN;
    nN->content=x;
    nN->aNext=oH;
    return nN;
}
SN* pop(SN* oH){
    if(oH==nullptr){
        return nullptr;
    }
    SN* nextNode;
    nextNode=oH->aNext;
    delete oH;
    return nextNode;
}
void deleteStack(SN* aH){
    while(aH!=nullptr){
        aH=pop(aH);
    }
}
SN* copyStack(SN* aT){
    if(aT==nullptr){return nullptr;}
    SN* aNew=new SN;
    aNew->content=aT->content;
    aNew->aNext=copyStack(aT->aNext);
    return aNew;
}
class CCAccount{
private:
    double balance;
    SN* aTT;
public:
    std::string cName;
    CCAccount();
    CCAccount(const std::string &);
    CCAccount(const CCAccount& );
    CCAccount& operator=(const CCAccount& );
    CCAccount(CCAccount&& );
    CCAccount& operator=(CCAccount&& );
    void printStatement() const;
    void addCharge(double );
    ~CCAccount();
};
CCAccount::CCAccount(){
    aTT=nullptr;
    balance=0.0;
    cName="NoNameYet";
    std::cout<<"Constructor (default)\n";
}
CCAccount::CCAccount(const std::string& n){
    aTT=nullptr;
    balance=0.0;
    cName=n;
    std::cout<<"Constructor (with added name)\n";
}
CCAccount::CCAccount(const CCAccount& b){
    cName=b.cName; balance=b.balance;
    aTT=copyStack(b.aTT);
    std::cout<<"Copy constructor\n";
}
CCAccount& CCAccount::operator=(const CCAccount& b){
    if(&b!=this){
        cName=b.cName; balance=b.balance;
        deleteStack(aTT);
        aTT=copyStack(b.aTT);
    }
    std::cout<<"Copy assignment\n";
    return *this;
}
CCAccount::CCAccount(CCAccount&& b){
    cName=b.cName;
    balance=b.balance;
    aTT=b.aTT;
    b.aTT=nullptr;
    std::cout<<"Move constructor\n";
}
CCAccount& CCAccount::operator=(CCAccount&& b){
    if(&b!=this){
        cName=b.cName;
        balance=b.balance;
        deleteStack(aTT);
        aTT=b.aTT;
        b.aTT=nullptr;
    }
    std::cout<<"Move assignment\n";
    return *this;
}
CCAccount::~CCAccount(){
    while(aTT!=nullptr){aTT=pop(aTT);}
    std::cout<<"Destructor for "<<cName<<"\n";
}
void CCAccount::addCharge(double x){
    balance+=x;
    aTT=push(aTT,x);
}
void CCAccount::printStatement() const{
    std::cout<<"Customer name: "<<cName<<"\n";
    std::cout<<"Balance: "<<balance<<"\n";
    std::cout<<"Transactions: ";
    SN* runner=aTT;
    while(runner!=nullptr){
        std::cout<<runner->content<<" ";
        runner=runner->aNext;
    }
    std::cout<<"\n";
}
CCAccount makeFunnyAccount(CCAccount x, CCAccount& y, CCAccount z){
    CCAccount funny;
    funny.cName=x.cName+y.cName+z.cName;
    funny.addCharge(100.0);
    funny.addCharge(50.0);
    return funny;
}
int main(){
    CCAccount p; CCAccount q; CCAccount r;
    p.cName="P"; q.cName="Q"; r.cName="R";
    CCAccount w;
    w=makeFunnyAccount(p,q,r);
    w.printStatement();
    return 0;
}
Problem 3. What would happen if assignment operator returned the copy of the object instead of the reference?

Problem 4. What would happen if assignment operator returned the constant reference instead of the traditional non-const reference?

11. Final remarks and summary

It may look that our story about object oriented programming was long. It was just an introduction and there is a lot that was missing and that you will have to learn in your later classes, or on your own. We have learned these concepts: classes; objects; members of classes; attributes; methods; public and private access levels; overloading of constructors, copy constructors, move constructors, copy assignments, and move assignments.

In the chapter 21. Inheritance, you will learn about one more access level, called protected.

We have learned that move constructor and move assignment are applied in steps 1 and 3 of the return statement. We called their arguments temporary objects. The formal name for the temporary object is rvalue reference. We will not go deeply into details about rvalues. We will just cover the basics here, so you can hopefully get sufficient motivation to read on your own. All variables are divided into two categories: lvalues and rvalues. Every variable can appear on the right side of the assignment operator. A variable is called lvalue if it can also appear on the left side of the assignment operator. A variable is called rvalue if it can only appear on the right side. For example, 5+7 is rvalue. Another example of rvalue is makeFunnyAccount(x,y,z). The objects x, y, and z are lvalues.

We can point out one informal rule in C++ object-oriented programming. It is called Rule of Five: These five methods: copy constructor, copy assignment, move constructor, move assignment, and destructor go together. If one is overloaded, then all five have to be overloaded - otherwise the code has vulnerabilities and bugs.

You can now do your own search and look for a slightly more advanced alternative - Rule of Four and a Half, which performs the implementation of copy-and-swap.

Another topic that was not covered in these notes is perfect forwarding, which becomes relevant when making more serious and lengthy programs. It is beyond the scope of these notes, but you will able to read about it once you become expert in solving the problems that are included here.

12. Practice

Problem 1.

Create a class BagOfPoints that represents a collection of points in two-dimensional space. Its private attributes are numPoints, which represents the total number of points, and two sequences sequenceX and sequenceY that are of type double*. The declaration of the class should be

class BagOfPoints{
private:
   double *sequenceX;
    double *sequenceY;
    int numPoints;
public:
    BagOfPoints(const int & =0);
    BagOfPoints(const BagOfPoints &);
    BagOfPoints(BagOfPoints&&);
    void operator=(const BagOfPoints &);
    void operator=(BagOfPoints&&);
    ~BagOfPoints();
    double getX(const int &) const;
    double getY(const int &) const;
    int getLength() const;
    void setTerm(const int &, const double &, const double &);
    void setLength(const int &);
};
  • (a) The methods getX(const int& k) and getY(const int& k) should return the \(x\) and \(y\) coordinates of the \(k\)-th point.
  • (b) The method getLength() should return the attribute numPoints.
  • (c) The method setTerm(const int & i, const double &x, const double &y) should set the coordinates of the \(i\)-th point to x and y.
  • (d) The method setLength(const int &n) should change the length of the sequences to \(n\). Keep in mind that this means that the sequences have to be re-allocated. For example if the old length was \(7\) and the new is \(107\), you may have only allocated the blocks of length \(7\) for the pointers sequenceX and sequenceY. You need to create new memory locations, copy the appropriate values of sequences sequenceX and sequenceY, re-assign the pointers sequenceX and sequenceY to point to the new locations, and delete the old memory to prevent the memory leak.
  • (e) The method bagOfPoints(const int& k); should be the default constructor that sets the number of points to \(k\) and allocates the memory for the sequences sequnceX and sequenceY. The methods BagOfPoints(const BagOfPoints& copyFrom); and void operator=(const BagOfPoints& copyFrom); are copy constructor and copy assignment operator. They should allocate the memory for sequenceX and sequenceY to be the exact size as the corresponding sequences in copyFrom. However, they should be at new locations. The values copyFrom.sequenceX[i] should be copied to sequenceX[i], and analogous should hold for copyFrom.sequenceY[i].
  • (f) The methods BagOfPoints(BagOfPoints&& moveFrom); and void operator=(BagOfPoints&& moveFrom); should be the move constructor and move assignment operator. Instead of allocating new memory for the sequences sequenceX and sequenceY, the move constructor and move assignment operator should instead just position the pointers sequenceX and sequenceY to the addresses of the corresponding pointers of the object moveFrom. However, the object moveFrom should then have its pointers set to nullptr so there is no error when destructor tries to free the memory.
  • (g) The method ~BagOfPoints() should be the destructor and should free the memory occupied by sequences sequenceX and sequenceY

In addition to the class and methods described above, create a function BagOfPoints reflectPoints(const BagOfPoints& bag); that multiplies with \(-1\) every \(x\)-coordinate and every \(y\)-coordinate of every point from the bag. Create a function int main() in which the user first enters the number of points he/she wishes to have in the bag, and then the user inserts the coordinates of these points. Based on the user input create an object bP that corresponds to the bag of points that user has entered. Create an object bPReflected that corresponds to the reflected bag of points obtained from the function reflectPoints. Discuss which constructors and/or assignment operators were called during the execution of your program.

Problem 5.

What is printed on standard output when the following program is compiled with the flag -fno-elide-constructors and then executed? Provide a rigorous justification for your answer.

// mysteryPrinting.cpp
 // compile with
 // c++ mysteryPrinting.cpp -o mPrinting -std=c++11 -fno-elide-constructors
 // execute with
 // ./mPrinting
 #include<iostream>
 class Wolf{
 private:
      std::string name;
 public:
     Wolf(const std::string & = "");
     Wolf(const Wolf &);    
     Wolf(Wolf &&);
     void operator=(const Wolf &);
     void operator=(Wolf &&);
     virtual ~Wolf();
 };
 Wolf::Wolf(const std::string & s){
     name=s;
 }
 Wolf::Wolf(const Wolf& x){
     name="A"+x.name;
 }
 Wolf::Wolf(Wolf&& x){
     name="B"+x.name;
     x.name+="C";
}
 void Wolf::operator=(const Wolf & x){
     name+="D"+x.name;
 }
 void Wolf::operator=(Wolf && x){
     name+="E"+x.name;
     x.name+="F";
 }
 Wolf::~Wolf(){
     std::cout<<name<<std::endl;
 }
 Wolf whisper(Wolf z){
     Wolf w("G");
     Wolf r=z;
     return w;
 }
 int main(){
     Wolf a("X");
     Wolf b("Y");
     b=whisper(a);
     return 0;
 }