Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 28

Object-Oriented Programming Language

12/14/2016
12.1 Dynamic Memory

In its most elementary form, C++ dynamic memory is managed through a pair of operators:

- new, which allocates, and optionally initializes, an object in dynamic memory and returns
a pointer to that object

- delete, which takes a pointer to a dynamic object, destroys that object, and frees the
associated memory.

int * p1 = new int;


*p1 = 1001;

int * p2 = new int(1001); // *p2 = 1001

int * p3 = new int(); // value initialized to 0; *p3=0

delete p1;

delete p2;

delete p3;

The new operator computes the size of the requested memory. In this case, it takes an integer,
and it returns enough memory to hold an integer value.

int * p1 = new int;


*p1 = 1001;

delete p1;

p1 is set to point to that memory and now p1 and the associated code that uses it become the
owner of that memoryin other words, the code that uses p1 must eventually return this
memory back to the free store, an operation called freeing the memory.

Until p1 is freed, the memory that is pointed to will be marked as in-use and will not be
given out again. If you keep allocating memory and never free it, you will run out of memory.
The delete operation frees up the memory allocated through new.

Before C++ introduces STL, dynamic memory via the mechanism of new and delete is the
only way to create an array dynamically (e.g., std::vector<T>). You can dynamically
allocate an array of memory using new and assign that memory to a pointer:

int *p_numbers = new int[8];


1
Object-Oriented Programming Language
12/14/2016

Using the array syntax as the argument to new tells the compiler how much memory it needs
enough for an 8 element integer array. Now you can use p_numbers just as if it pointed to
an array. Unlike built-in arrays, though, you need to free the memory pointed to by
p_numbers. To free the memory, there is a special syntax for the delete operator:

delete[] p_numbers;

The brackets tell the compiler that the pointer points to an array of values, rather than a single
value.

In-class Exercise 12.1: write a program to dynamically allocate the requested memory for a
string array via new, change all the values to C++ and output the contents. Below is a
sample run:

A:
#include <iostream>
#include <string>

using namespace std;

int main()
{
cout << "Enter the size of the string array: ";
int num;
cin >> num;
string* parr = new string[num];
for (unsigned i = 0; i < num; ++i)
parr[i] = "C++";
cout << endl;
cout << "The new contents of the string array are: " << endl;
for (unsigned i = 0; i < num; ++i)
cout << parr[i] << " ";
cout << endl;
delete [] parr;
return 0;
}

This what we need to know so far. More on the good, the bad and the ugly of dynamic
memory will be covered later if the time permitted.

2
Object-Oriented Programming Language
12/14/2016

7.6 static Class Member

static class members are data and functions that are associated with the class itself, rather
than with the objects of the class. In contrast, the members associated with the object are
called instance members.

Now consider a class Fred

class Fred {
public:
static void f();// Member function associated with the class
void g(); // Member function associated with an individual object of the
class.

protected:
static int x; // Data member associated with the class
int y; // Data member associated with an individual object of the class
};

3
Object-Oriented Programming Language
12/14/2016
The class has a static data member x and an instance data member y. There is only one
copy of Fred::x regardless of how many Fred objects are created (including no Fred
objects), but there is one y per Fred object. Thus x is said to be associated with the class and
y is said to be associated with an individual object of the class. Similarly class Fred has a
static member function f() and an instance member function g().

Example: a bank account class Account is a good example to illustrate the need for class
static members. The Account class might need a static data member to represent the
current prime interest rate. In this case, wed want to associate the rate with the class, not
with each individual object. From an efficiency standpoint, thered be no reason for each
object to store the rate. Much more importantly, if the rate changes, wed want each object to
use the new value.

class Account {
public:
void calculate() { amount += amount * interestRate; }
static double rate() { return interestRate; } //get
static void rate(double); //set
private:
std::string owner;
double amount;
static double interestRate;
};

Access Class static Members

We can access a static member directly through the scope operator:

double r;
r = Account::rate(); // access a static member using the scope
operator

Even though static members are not part of the objects of its class, we can use an object,
reference, or pointer of the class type to access a static member:

Account ac1;
Account& ac2 = ac1;
Account* ac3 = &ac1;
// equivalent ways to call the static member rate function
double r;
r = ac1.rate(); // through an Account object
r = ac2.rate(); // through an Account reference
r = ac3->rate(); // through a pointer to an Account object

Other Account class member functions can use static members directly, without the scope

4
Object-Oriented Programming Language
12/14/2016
operator:

class Account {
public:
void calculate() { amount += amount * interestRate; }
private:
static double interestRate;
// remaining members as before
};

Define static Members

As with any other member function, we can define a static member function inside or
outside of the class body.

Best Practice
Everything except instance data members must be defined somewhere; for static data
member and static member function, we typically do the definition in the corresponding
cpp source file (Account.cpp):

#include "Account.h"
double Account::interestRate = 0.02;

void Account::rate(double r){


interestRate = r;
}

When we define a static member outside the class, we do not repeat the static
keyword. The keyword appears only with the declaration inside the class body:

double Account::interestRate = 0.02;

Example: let us put together an Account class and illustrate its usage of static class member.

Account.h
#ifndef ACCOUNT_H
#define ACCOUNT_H
#include <string>

class Account {
public:
Account() = default;
Account(std::string name, double initAmount) :
owner(name), amount(initAmount){ }
5
Object-Oriented Programming Language
12/14/2016
void calculate() { amount += amount * interestRate; }
static double rate() { return interestRate; } //get
static void rate(double); //set
double getAmount() { return amount; }
std::string getOwner(){ return owner; }
private:
std::string owner = "Anonymous";
double amount = 0.0;
static double interestRate;
};
#endif

Account.cpp
#include "Account.h"

double Account::interestRate = 0.04;

void Account::rate(double r) {
interestRate = r;
} //set

mainDeposit.cpp
#include "Account.h"
#include <iostream>
#include <vector>

using namespace std;

int main(){
Account a = Account("John", 100);
Account b = Account("Mary", 1000);
vector<Account> coll = {a , b};
cout << "The current interesting rate is: " <<
Account::rate()
<< endl;
cout << "The amount in each account is: " << endl;
for (auto e : coll){ // copy and will not change e
e.calculate();
cout << e.getOwner() << ": " << e.getAmount() << endl;
}
cout << endl;

Account::rate(0.05);
cout << "The current interesting rate is: " <<
Account::rate()
<< endl;
cout << "The amount in each account is: " << endl;
for (auto e : coll){ // copy and will not change e
e.calculate();
cout << e.getOwner() << ": " << e.getAmount() << endl;
}
cout << endl;

return 0;

6
Object-Oriented Programming Language
12/14/2016
}

Remark: static member function vs. ordinary member function

An ordinary member function declaration of a class specifies three logically distinct things:
1. The function can access the private part of the class declaration.
2. The function is in the scope of the class.
3. The function must be invoked from an object (or the function has a this pointer)

By declaring a member function static, we give it the first two properties only. By
declaring a function friend, we give it the first property only.

7
Object-Oriented Programming Language
12/14/2016
Chapter 13: Copy Control

13.1 Copy, Assign and Destroy

Well cover the most basic operations, which are the copy constructor, copy-assignment
operator, and destructor. The move operations are introduced by the new standard and will
not be covered herein.

13.1.1 Copy Constructor

The copy constructor has a signature like this:

class MyClass {
public:
...
MyClass(const MyClass &rhs);
...
}

MyClass a;
...
MyClass b = a; // Same as MyClass b(a);

A copy constructor is invoked whenever a new object is created and initialized to an existing
object of the same kind. This happens in several situations. The most obvious situation is
when you explicitly initialize a new object from an existing object.

// calls string(const string &)


string ditto(motto);
string metoo = motto;
string also = string(motto);
string* pString = new string(motto);

The last example initializes an anonymous object to motto and assigns the address of the
new object to the pString pointer. It is equivalent to:

string* pString = new string();


*pString = motto;

Less obviously, a compiler uses a copy constructor whenever a program generates copies
of an object. In particular, its used when we pass an object as an argument to a parameter of
non-reference type (function passes an object by value) or when a function returns an object.

8
Object-Oriented Programming Language
12/14/2016
string fcn(Foo f) {
string s;
// do something
return s;
}

string obj = fcn(Foo f);

Q: The fact that the copy constructor is invoked for non-reference parameters explains why
the copy constructors own parameter must be a reference. If that parameter were not a
reference, then the call would never succeed, why?
A:
To call the copy constructor, wed need to use the copy constructor to copy the argument, but
to copy the argument, wed need to call the copy constructor, and so on indefinitely.

When writing your own class, you can easily check whether the copy constructor has been
called.

Example:
CopyConstructor.cpp
#include <iostream>
using namespace std;

class MyClass{
public:
MyClass() = default ;
MyClass(const MyClass& rhs){
*this = rhs;
cout << "MyClass::MyClass(const MyClass& rhs): " << ref++
<< " time(s)" << endl;
}
static int ref;
};

int MyClass::ref = 1;

MyClass foo(MyClass m) {
MyClass m3(m);
cout << "Before returning from foo" << endl;
return m3;
}

int main(){
MyClass m1;
MyClass m2 = m1;
cout << "Before calling foo" << endl;
MyClass m4 = foo(m1);
return 0;
}

9
Object-Oriented Programming Language
12/14/2016
Q: what are the outputs?
A:

Remark: One of the major features in the new standard C++11 is the ability to move rather
than copy an object. As we have seen, copies are made in many circumstances. In some of
these circumstances, an object is immediately destroyed after it is copied. In those cases,
moving, rather than copying, the object can provide a significant performance boost. See
Section 13.6 for detailed coverage on move.

(see ppt)

Assignment Operator in Depth

The assignment operator has a signature like this:

class MyClass {
public:
...
MyClass & operator=(const MyClass &rhs);
...
}

MyClass a, b;
...
b = a; // Same as b.operator=(a);

Notice that the = operator takes a const-reference to the right hand side of the assignment.
The reason for this should be obvious, since we don't want to change that value; we only
want to change what's on the left hand side.

Also, you will notice that a reference is returned by the assignment operator. This is to allow
operator chaining. You typically see it with primitive types, like this:

int a, b, c, d, e;
a = b = c = d = e = 42;

10
Object-Oriented Programming Language
12/14/2016
This is interpreted by the compiler as:

a = (b = (c = (d = (e = 42))));

In other words, assignment is right-associative. The last assignment operation is evaluated


first, and is propagated leftward through the series of assignments. Specifically:

e = 42 assigns 42 to e, then returns e as the result


The value of e is then assigned to d, and then d is returned as
the result
The value of d is then assigned to c, and then c is returned as
the result
etc.

Now, in order to support operator chaining, the assignment operator must return a reference
to the left-hand side of the assignment.

Q: If we return an object rather than a reference, we know a new object will be created. Will
this affect the results of operator chaining a=b=c if we return an object instead of a
reference? Will it affect the results of operator chaining (a=b)=c?
A:
Case 1: the results are ok but wield
a = b = c
a = (b = c) b is now equal to c
a = tmp_b a is now equal to tmp_b equal to c

Case 2: the results are not ok (but can be compiled)


(a = b) = c a is equal to b
tmp_a = c tmp_a is equal to c

Remark: for simple chain assignment operation a = b = c, both return non-const


reference or non-const object works but we prefer non-const reference for the reason of
efficiency.

class MyClass {
public:
MyClass& operator=(const MyClass &rhs) {return *this;}
// incomplete implementation of assignment operator
};

int main()
{
MyClass a, b, c;
a = b = c;
}

11
Object-Oriented Programming Language
12/14/2016

Quick Concept Check: Let us now design some running codes to test the above statements

#include <iostream>
using namespace std;

class MyClass {
public:
MyClass(int i):data(i){}
// act like synthesis assignment operator
MyClass& operator=(const MyClass &rhs) {
if (this != &rhs) data = rhs.data;
return *this;
}
int print(){return data;}
private:
int data;
};

int main()
{
MyClass a(1), b(2), c(3);
a = b = c;
cout << a.print() << " " << b.print() << " " << c.print() <<
endl;
MyClass d(1), e(2), f(3);
(d=e)=f;
cout << d.print() << " " << e.print() << " " << f.print() <<
endl;
return 0;
}

Q: what are the outputs?


A:
3, 3, 3
3, 2, 3
And this is what we expected from the built-in types.

Q: what are the outputs if we return an object?


MyClass operator=(const MyClass &rhs) {
A:
3, 3, 3
2, 2, 3

Now, one more very important point about the assignment operator:

YOU MUST CHECK FOR SELF-ASSIGNMENT!

This is especially important when your class does its own memory allocation. Here is why:
12
Object-Oriented Programming Language
12/14/2016
The typical sequence of operations within an assignment operator is usually something like
this:

MyClass& MyClass::operator=(const MyClass &rhs) {


// 1. Deallocate any memory that MyClass is using internally
// 2. Allocate some memory to hold the contents of rhs
// 3. Copy the values from rhs into this instance
// 4. Return *this
}

Now, what happens when you do something like this:

MyClass mc;
...
mc = mc; // BLAMMO.

You can hopefully see that this would wreak havoc on your program. Because mc is on the
left-hand side and on the right-hand side, the first thing that happens is that mc releases any
memory it holds internally. But, this is where the values were going to be copied from, since
mc is also on the right-hand side! So, you can see that this completely messes up the rest of
the assignment operator's internals.

The easy way to avoid this is to CHECK FOR SELF-ASSIGNMENT. The correct and safe
version of the MyClass assignment operator would be this:

MyClass& MyClass::operator=(const MyClass &rhs) {


// Check for self-assignment!
if (this == &rhs)// Same object?
return *this; // Yes, so skip assignment, and just return
*this.

... // Deallocate, allocate new space, copy values...

return *this;
}

Or, you can simplify this a bit by doing:

MyClass& MyClass::operator=(const MyClass &rhs) {

// Only do assignment if RHS is a different object from this.


if (this != &rhs) {
... // Deallocate, allocate new space, copy values...
}

return *this;
}

13
Object-Oriented Programming Language
12/14/2016

In summary, the guidelines for the assignment operator are:

1. Take a const-reference for the argument (the right-hand side of the assignment).
2. Return a reference to the left-hand side, to support safe and reasonable operator
chaining. (Do this by returning *this.)
3. Check for self-assignment, by comparing the pointers (this to &rhs).

(see ppt)

Destructor

13.1.4 The Rule of Three/Five

One rule of thumb to use when you decide whether a class needs to define its own versions of
the copy-control members is to decide first whether the class needs a destructor.

Often, the need for a destructor is more obvious than the need for the copy constructor or
assignment operator. If the class needs a destructor, it almost surely needs a copy constructor
and copy-assignment operator as well. For example, consider a simple HasPtr class:

class HasPtr {
public:
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0) { }
private:
std::string *ps;
int i;
};

It is obvious that we will need to our own destructor to clean the memory. What may be less
clearbut what our rule of thumb tells usis that HasPtr also needs a copy constructor
and assignment operator.

Consider what would happen if we gave HasPtr a destructor but used the synthesized
versions of the copy constructor and copy-assignment operator:

class HasPtr {
public:
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0) { }
~HasPtr(){delete ps;}
private:
14
Object-Oriented Programming Language
12/14/2016
std::string *ps;
int i;
};

In this version of the class, the memory allocated in the constructor will be freed when a
HasPtr object is destroyed. Unfortunately, we have introduced a serious bug! This version
of the class uses the synthesized versions of copy and assignment. Those functions copy the
pointer member, meaning that multiple HasPtr objects may be pointing to the same
memory:

HasPtr f(HasPtr hp) // HasPtr passed by value, so it is copied


{
HasPtr ret = hp; // copies the given HasPtr
// process ret
return ret; // ret and hp are destroyed
}

Q: So what is wrong?
A:
When f returns, both hp and ret are destroyed and the HasPtr destructor is run on each of
these objects. That destructor will delete the pointer member in ret and in hp. But these
objects contain the same pointer value. This code will delete that pointer twice, which is an
error and its behavior is undefined.

In addition, the caller of f may still be using the object that was passed to f:

HasPtr p("some values");


f(p); // when f completes, the memory to which p.ps points is
freed
HasPtr q(p); // now both p and q point to invalid memory!

Let us put the above codes together to see the effect:

BigThreeMissing.cpp
Example: Copy Constructor Is Missing

#include <string>
#include <iostream>

class HasPtr {
public:
HasPtr(const std::string &s = std::string()):
ps(new std::string(s)), i(0) { }
~HasPtr(){delete ps;}
void printString() {std::cout << *ps;}

15
Object-Oriented Programming Language
12/14/2016
private:
std::string *ps;
int i;
};

HasPtr f(HasPtr hp) // HasPtr passed by value, so it is copied


{
HasPtr ret = hp; // copies the given HasPtr
// process ret
return ret; // ret and hp are destroyed
}

using namespace std;

int main()
{
HasPtr p("some values");
p.printString();
f(p); // when f completes, the memory to which p.ps points is
freed
cout << endl;
cout << "After f: " << endl;
p.printString();
cout << endl;
HasPtr q(p); // now both p and q point to invalid memory!
cout << "After q(p): " << endl;
p.printString();
cout << endl;
q.printString();
cout << endl;
}

And we get the following:

If we add the proper copy constructor into the code:

HasPtr(const HasPtr& orig) {


ps = new std::string();
*ps = *orig.ps;
i = orig.i;
}

16
Object-Oriented Programming Language
12/14/2016
We will then get the expected results:

17
Object-Oriented Programming Language
12/14/2016
Chapter 14: Overloaded Operations and Conversions

14.2 Input and Output Operators


Motivation: Streams for Built-in Types

We can imagine the declaration of the overloading operators in class ostream looks like
the following:

class ostream {
public:
ostream& operator<< (char);
ostream& operator<< (int);
ostream& operator<< (double);
...
};

And implementation might look like:

ostream& operator<< (char)


{
// ... do something at low-level functions to output
characters
return *this;
}

Remark: here the return type has to be ostream& and not just ostream. Using an ostream
return type would require calling the ostream copy constructor, and, as it turns out, the
ostream class Does Not have a copy constructor.

Food for Thoughts: If you are the class writer for ostream, how can you design the class so
it will not have a copy constructor?

Declaration of the overloading operators in class istream

class istream {
public:
istream& operator>> (char&);
istream& operator>> (int&);
istream& operator>> (double&);
...
};

Q: again we return the reference of the first operand for the same reason as cout. But why
do we pass a non-const reference? That is, we do:

18
Object-Oriented Programming Language
12/14/2016

istream& operator>> (char&);

but not

istream& operator>> (char);


istream& operator>> (const char&);

A: Think about the typical way of using cin:

char ans;
cin >> ans; // cin.operator>>(ans);

We want to modify ans: thats the whole purpose of cin.

(see ppt)

Streams for User-defined Types: Non-Member Implementation

C++ Language Feature for Interpreting the Binary Operator

When compiler sees:

a*b

1. (Member Implementation) It can be interpreted in an object-oriented fashion so the first


operand is the receiving object and the second operand is the passing argument of the
message.

a.operator*(b)

2. (Nonmember Implementation) It can also be interpreted as a global function combined


two operands, both are the passing arguments.

operator*(a, b)

Thus for the user-defined type, an I/O operator (<< or >>) must be defined outside the user-
defined class, or more precisely as a global function.

#ifndef FRACTION_H
#define FRACTION_H

19
Object-Oriented Programming Language
12/14/2016
class Fraction {
public:
...
private:
int numer;
int denom;
};

inline
std::ostream& operator << (std::ostream& strm, const Fraction& f)
{
strm << f.numer << / << f.denom ;
return strm;
}

...
#endif

We will talk about inline later. Notice that the non-member implementation of operator
overloading usually need to read or write the nonpublic data members. As a consequence,
classes often make these operator overloading global function friends.

Summary
If we want to use the overloaded operators to do IO for our own types, we must define
them as a nonmember functions.
IO operators usually read or write the nonpublic data members. As a consequence,
classes often make the IO operators friends.

In-class Exercise 14.1: Fractions are numbers that can be written in the form a/b, where a and
b are integers. a is known as the numerator and b the denominator. Implement a class
Fraction and allow their objects to support the following functionalities:

Ex14-1.cpp

#include <iostream>
#include "Fraction1.h"
using namespace std;

int main()
{
Fraction a(4,3);
cout << "The fraction is: " << a << endl;
return 0;
}

20
Object-Oriented Programming Language
12/14/2016

A:

#ifndef FRACTION_H
#define FRACTION_H
#include <iostream>

class Fraction {
friend std::ostream& operator << (std::ostream& out, const
Fraction& f);
public:
Fraction(int n, int d): numer(n), denom(d){}
private:
int numer;
int denom;
};

inline
std::ostream& operator << (std::ostream& out, const Fraction& f)
{
out << f.numer << '/' << f.denom;
return out;
}

#endif // FRACTION_H

(see ppt)

(6.5.2) C++ inline Function

Inline functions are a C++ enhancement designed to speed up programs. The primary
distinction between normal functions and inline functions is not in how you code them but in
how the C++ compiler incorporates them into a program.

Normal function calls involve having a program jump to another address (the functions
address) and then jump back when the function terminates.

C++ inline functions provide an alternative. In an inline function, the compiled code is in
line with the other code in the program. That is, the compiler replaces the function call
with the corresponding function code.

With inline code, the program doesnt have to jump to another location to execute the code
and then jump back. Inline functions thus run a little faster than regular functions, but they

21
Object-Oriented Programming Language
12/14/2016
come with a memory penalty (see illustration below).

You should be selective about using inline functions. To use this feature, you must take at
least one of two actions:

Preface the function declaration with the keyword inline.


Preface the function definition with the keyword inline. (this is a typical way)

The compiler does not have to honor your request to make a function inline. It might decide
the function is too large or notice that it calls itself (recursion is not allowed or indeed
possible for inline functions), or the feature might not be turned on or implemented for your
particular compiler.

Summary: in an inline function, the compiled code is in line with the other code in
the program. That is, the compiler replaces the function call with the corresponding function
code.
22
Object-Oriented Programming Language
12/14/2016

For normal non-inline functions: if you define your function twice in two source files, you
will get a linker error: a function can be defined only once.

For inline functions: their definitions can be duplicated. Compiler is not allowed to
complain about multiple definitions. This is because in order to be able to inline a function,
compiler has to have its definition available at any time, in any compiled source file. In order
to provide that, for inline functions it is allowed and in fact required that their definition (their
body) is defined in every translation unit in which the function is used.

Duplicated definitions, however, must define exactly same function. Not only should they be
identical token by token, but the same name of a variable must refer to the very same object
in each source file.

The best practice to indicate a function is an inline function is to preface the function
definition with the keyword inline. All templates are already inline.

Example: let us take a look of a very simple class Fraction with output operator
overloading implemented as an inline function. We purposely put the implementation of
the constructor in Fraction.cpp so Fraction.h will be included twice: one by the main
client code and the other by Fraction.cpp.
Fraction.h
#ifndef FRACTION_H
#define FRACTION_H
#include <iostream>

class Fraction {
friend std::ostream& operator << (std::ostream& out, const
Fraction& f);
public:
Fraction(int n, int d);
private:
int numer;
int denom;
};

inline
std::ostream& operator << (std::ostream& out, const Fraction& f)
{
out << f.numer << '/' << f.denom;
return out;
}

23
Object-Oriented Programming Language
12/14/2016

#endif // FRACTION_H
Fraction.cpp
#include "Fraction.h"

Fraction::Fraction(int n, int d) {
numer = n;
denom = d;
}

mainFraction.cpp
#include <iostream>
#include "Fraction.h"
using namespace std;

int main()
{
Fraction a(4, 3);
cout << "The fraction is: " << a << endl;
return 0;
}

But if you remove inline from output operator overloading definition in Fraction.h, you
will meet a linking error indicating multiple definitions.

mainFraction.obj : error LNK2005: "class


std::basic_ostream<char,struct std::char_traits<char> > & __cdecl
operator<<(class std::basic_ostream<char,struct
std::char_traits<char> > &,class Fraction const &)" (??6@YAAAV?
$basic_ostream@DU?
$char_traits@D@std@@@std@@AAV01@ABVFraction@@@Z) Fraction.obj

14.3 Arithmetic and Relational Operators

We define the arithmetic (+ - * /) and relational operators (< > <= >= !=
==) as non-member functions in order to allow conversions for either the left- or right-hand
operand.

y = x + 3;
y = 3 + x;

The arithmetic operators shouldnt need to change the state of either operand, so the

24
Object-Oriented Programming Language
12/14/2016
parameters are references to const.

An arithmetic operator usually generates a new value that is the result of a computation on its
two operands. That value is distinct from either operand and is calculated in a local variable.
The operation returns a copy of this local as its result.

Classes that define an arithmetic operator generally define the corresponding compound
assignment operator as well. When a class has both operators, it is usually more efficient to
define the arithmetic operator to use compound assignment:

Sales_item operator+(const Sales_item &lhs, const Sales_item


&rhs)
{
Sales_item sum = lhs; // copy data members from lhs into sum
sum += rhs; // add rhs into sum
return sum;
}

Compound Assignment Operator

Compound assignment operators are usually defined to be member functions. For


consistency with the built-in compound assignment, these operators should return a
reference to their left-hand operand.

Sales_item& Sales_item::operator+=(const Sales_item &rhs)


{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}

Equality (==) and Less Than (<) Operators

Class that will be used as the key types of an associative container MUST define the <
operator. In general, a class should define the equality ( ==) and less than (<) operators since
many STL algorithms assume these operators exist. For example, sort uses < and find uses
==.

In-class Exercise 14.2: Write a Fraction class so we can insert its objects into a set for
automatic sorting and output using cout.

Ex14-2.cpp

25
Object-Oriented Programming Language
12/14/2016
#include <iostream>
#include <set>
#include "Fraction2.h"

using namespace std;

int main()
{
set<Fraction> coll =
{Fraction(2,3),Fraction(1,2),Fraction(3,4),Fraction(1,5),
Fraction(4,3)};
cout << "Fractions in a sorting order are: ";
for (auto e : coll)
cout << e << " ";
cout << endl;
return 0;
}

A:

#ifndef FRACTION_H
#define FRACTION_H
#include <iostream>

class Fraction {
friend std::ostream& operator << (std::ostream& out, const
Fraction& f);
friend bool operator < (const Fraction& f1, const Fraction& f2);
public:
Fraction(int n, int d):numer(n),denom(d){}
private:
int numer;
int denom;
};

inline
std::ostream& operator << (std::ostream& out, const Fraction& f)
{
out << f.numer << '/' << f.denom;
return out;
}

inline
bool operator < (const Fraction& f1, const Fraction& f2) {
if (f1.numer*f2.denom < f2.numer*f1.denom) return true;
else return false;
// return (f1.numer*f2.denom < f2.numer*f1.denom) ? true :
false;
}

26
Object-Oriented Programming Language
12/14/2016
#endif // FRACTION_H

14.5 Subscript Operator

Classes that represent containers from which elements can be retrieved by position often
define the subscript operator, operator[]. The subscript operator MUST be a member
function.

A class that defines subscript needs to define two versions: one is a non-const member
function that returns a reference (so that the subscript can appear on either side of assignment
operator) and the other is a const member function that returns a const reference (so that
when we subscript a const object, it cannot appear on the left hand side of the assignment
operator).

FooVec.cpp

#include <iostream>
#include <vector>
using namespace std;

class FooVec {
public:
int &operator[] (const size_t index) { return data[index] }
const int &operator[] (const size_t index) const
{
return data[index];
}
// other interface members
FooVec(int i = 0); // allocate space in vector<int> data
private:
std::vector<int> data;
// other member data and private utility functions
};

int main(){
FooVec f1(2);
f1[0] = 3;
f1[1] = f1[0];

const FooVec f2(1);


f1[1] = f2[0]; // ok
f2[0] = f1[1]; // not ok!
}

27
Object-Oriented Programming Language
12/14/2016

28

You might also like