Session 6 - Class Basics Contd : Deep Copies Custom Iostream Operators Conversion Operators

You might also like

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 55

SESSION 6 – CLASS BASICS

CONTD…
DEEP COPIES
CUSTOM IOSTREAM OPERATORS
CONVERSION OPERATORS
Event Loops - ( WS5 )
WinMain(){
while(!done){
if(‘R’)… // user pressed ‘R’
if(‘S’)… // user pressed ‘S’
if(‘L’)…
… [other keys]
… [other mouse clicks]

if(‘Clicked close’){
done = true;
}
}
Command line main()function

int main(){

step1();
step2();
step3();
cout << “done”;

return 0;
}
Detecting Keyboard Events ( WS5 )

bool sp; // S Pressed?


if (keys['S'] && !sp) {


sp = TRUE; // pressing the S key
… [game code goes here]
}
if (!keys['S']){
sp = FALSE; // not pressing the S key
}
Releas
Press ●
‘S’ && !sp Hold ●
‘S’ && sp
e

!‘S’ && !sp
Detecting Keyboard Events ( WS5 )

bool sp; // S Pressed?


if (keys['S'] && !sp) {


sp = TRUE; // pressing the S key
… [game code goes here]
}
if (!keys['S']){
sp = FALSE; // not pressing the S key
}
Order Order
ISBN* nbr; = ISBN* nbr;
(shallow copy)

ISBN
Shallow copies copy the ADDRESS of pointers
but they don’t copy the object they point TO.
(deep copy) Order
Order
ISBN* nbr; = ISBN* nbr;

ISBN = ISBN
Deep copies make copies of each data object
instead of just copying pointers to data objects.
If you want to do deep copies, you need to write
a copy constructor.

Order::Order(Order& source) {
// new ISBN object
this->isbn = ISBN(source.isbn);
ordered = source.ordered;
this->delivered = source.delivered;
// You don’t have to say “this”
}
If you want to do deep copies, you need to write a
copy constructor and an assignment operator.

Order::Order(Order& source) {
// new ISBN object
this->isbn = ISBN(source.isbn);
ordered = source.ordered;
this->delivered = source.delivered;
// You don’t have to say “this”
}
If you want to do deep copies, you need to write a
copy constructor and an assignment operator.

Order::operator= (Order& rhs) {


// new ISBN object
this->isbn = ISBN(rhs.isbn);
this->ordered = rhs.ordered;
this->delivered = rhs.delivered;
}
If you want to do shallow copies, you
don’t need a copy constructor.

Order Order
ISBN* nbr; = ISBN* nbr;
(shallow copy)

ISBN
Resources

• Resources are stored outside the memory


allocated to an object. 
• Instance variables that refer to resources
(resource instance variables) include pointers
to
 dynamic arrays in freestore memory, and
 file structures
Resource instance variables example

a pointer !
Copying
• If we were to make a shallow copy of a Student
object, the original and the copy would point to the
same resource: grade pointer
• If any changes the grades in the copy, the original
referred to the changed grades and no longer to the
original grades.

This behavior is not what we normally expect


from a copy.
Copying…
• For each resource variable, we obtain a pointer to a
new resource and copy the original into that
resource. 
• To enable deep copying, we overwrite the compiler
defaults for two member functions:
 the copy constructor and
 the assignment operator

If we do not define a copy constructor, the compiler


inserts one that makes a shallow copy.  If we do not
define an assignment operator, the compiler inserts
one that performs a shallow assignment. 
Copy Constructor

• The copy constructor copies information from


an existing object to a newly created object.
• The compiler calls this constructor whenever
an object is
 initialized,
 passed by value to a function parameter, or
 returned by value from a function
Copy Constructor…

• A copy constructor declaration:


Identifier ( const Identifier& );
 Identifier is the class name.  In the definition of the
copy constructor, we
 perform a shallow copy of the non-resource instance
variables,
 allocate fresh freestore memory for the resource
instance variable(s), and
 copy data pointed to by the original object to the
freshly allocated memory pointed to by the new
object.
Assignment Operator
• An assignment operator copies data from an existing object
into an existing object.
Identifier& operator=(const Identifier&);
(Identifier: is the name of the class of the right operand) 

In the definition, we:


– check for self-assignment
– deallocate previously allocated freestore memory
– allocate new freestore memory
– copy resource source data to freshly allocated memory
– copy non-resource instance variables to destination
variables
No Copies Allowed

If you want to make it impossible for


other people to write code that makes
copies of your object, write a copy
constructor and an assignment
operator and make them private.
Custom iostream Operators

int main () {
ISBN foo = ISBN(“9330440556”);

// How does cout know what to do with


// our ISBN object foo?

cout << foo; // It doesn’t… yet.


}
Custom iostream Operators

• An object can interact with input and output


streams in the same way that primitive data
types interact. 
• The istream and ostream classes of the
iostream library support object-oriented input
and output.
Custom iostream Operators…

• We extend the functionality of the extraction


(>>) and the insertion (<<) operators to
objects of our own design by overloading the
operators to accept our objects as rights
operands. 
Overloading the << operator

class Student {
int no;
int semester;
char grade[M+1];
public:
Student();
friend ostream& operator<<(ostream& os,
const Student& s);
};
Overloading the << operator

ostream& operator<<(ostream& os,


const Student& st) { 

os << st.no << ' ' << st.semester


<< ' ' << st.grade << endl;

return os;

}
Overloading the << operator

Int main () {

Student hans(00212,”ABCDF”);
cout << hans; // now it works!

}
Cascading

• Cascading is the concatenation of several


variables in a single statement interspersed
with the appropriate operator.
• The insertion and extraction operators are
overloaded in the istream and ostream
classes so as to enable cascading. 
Cascading…

Card a, b, c, d, e;

// how does the + operator


// do this:
a = b + c + d + e;
We need 2 operators:

// This one is for the first pair:


Operator+(Card L, Card R);

// This one is for the second,


third, etc.
Operator+(int , Card R);
Overloading the + operator

int operator+(int lhs,Card &rhs) {


return lhs + valToInt(rhs.val);
}
int main () {
Card a ('A', 's');
Card b ('K', 'd');
Card c ('3', 'h');
Card d ('9', 's');
int foo;

foo = a + b + c + d; // foo = 33
}

int operator+(int lhs,Card &rhs) {


return lhs + valToInt(rhs.val);
}
Returning A Reference

• Returning a reference from a function has two


intrinsic benefits:
 efficiency
 creates an lvalue
• Returning a reference copies a single address. 
(Returning the value at that address may involve
copying many instance variables, possibly even
deep copying, which is much more time
consuming.)
Overloading the << operator: Cascading

ostream& operator<<(ostream& os,


const Student& st) { 

os << st.no << ' ' << st.semester


<< ' ' << st.grade << endl;

return os; // return a reference

}
Student nobita(13443,”BDCDF”);
Student doraemon(33333,”AABAD”);

cout << doraemon << ‘ ‘ << nobita;


When the first operator<< runs, it returns a reference
to a Student object , so the next << can also work.

ostream& operator<<(ostream& os,


const Student& st) { 
os << st.no << ' ' << st.semester
<< ' ' << st.grade << endl;
return os; // return a reference
}
lvalue Creation
• The term lvalue originates in the description of an
assignment expression. 
• An assignment expression consists of a left operand, the
assignment operator and a right operand.  The left
operand must occupy some memory location where the
value of the right operand may be stored.  The left
operand cannot be a constant or a temporary expression. 
• Examples of lvalues include:
 variables
 objects
 array elements
 functions that return references
Dangling References

• Returning a reference to a local variable or a


parameter received by value (these go out of
scope when the function returns control to its
caller) is called a dangling reference. 
Variable Scope : a function that does
nothing
void doNothing () {
int a, b, c;
a = 5; b = 6, c = 10;
int d = a * b * c;
// when the function closes a, b,
c, and d get ~deleted.
}
Variable Scope: Dangling References

Card * addCards(Card a, Card b, Card)


{
Card c;
c = a + b;
return &c; // this doesn’t work
// because c gets deleted
// after the function closes
}
Variable Scope: no dangling reference

void addCards(Card a,
Card b, Card * c ) {

*c = a + b;
return c; // this works
// it works because c comes from
// outside the function,
// so it doesn’t get deleted
// when the function closes.
}
String Input overflow

char foo [10];


int bar = 5;

cin >> foo; // user types 13 chars

cout << bar; // error – bar has been


// corrupted by extra
// characters from foo.
String Input overflow…

• The Solution
 The string class addresses this indefinite-size
problem.  An object of the string class accepts as
many characters as the user enters and allocates
as much memory as is needed to store the set of
characters. 
 The string class requires #include <string> for the
prototypes.
String Input overflow…

istream& getline(istream&, string&, char);  


• The string class has two member functions for
translating strings into null-terminated C-style
strings:
 length() - the number of characters in the string
 c_str() - the address of the null-terminated C-style
version of the string.
String Class Example

#include <string>

int main( ) {
string str;
getline(cin, str);

}
Conversion Operators

Sometimes we need to convert an object of one


class to an object of another data type.

Student::operator int() const{


return no;

Student::operator int() const{
return no;

int main() {
int n;
Student hans(1234, “ABABF”);

n = hans; //convert Student to int

cout << n; // what output?



}
Derived Data Type Conversions
Design Considerations

• Conversion operators should be used


sparingly and their implementations kept
trivial. 

• Too many conversion operators make your


code difficult to read. 
Single-Argument Constructors

Conversion operators define implicit conversions


from the data type of the current object to
another data type. 

To define a conversion from a data type to the


data type of the current object, we use single-
argument constructors. 
class Student {

public:
Student();
Student(int);
set(int no, char grades);
};

Student::Student(int no) {
set(no, "");
} // single argument constructor
Student::Student(int no) {
set(no, "");
} // single argument constructor

// convert int to Student


Student harry = 1234;

cout << harry;


How does the compiler know what to do
when it sees this:

Student harry = 1234;

?
Conversion Sequence…
Student harry = 1234; // ??
• In searching for a conversion the compiler steps
through definite stages.  The compiler looks for
 an exact match (for example, int to int, Student to
Student),
 a promoted match (for example, char to int, float to
double),
 a standard conversion match (for example, int to
double, int to float),
 a derived data type conversion match (for example,
int to Student). ( This is the one it uses for the
example shown above.)
Explicit Constructors

• To suppress implicit conversions by a single-


argument constructor, we qualify the constructor as
explicit
explicit ClassIdentifier(dataType);

• For an explicit single-argument constructor, we


need to write
harry = Student(1234); 
instead of
harry = 1234; 
explicit Student::Student(int no) {
set(no, "");
}

// doesn’t work
Student harry = 1234;

// works
Student harry = Student(1234);

Why would we want to use “explicit”?


?

You might also like