0% found this document useful (0 votes)
7 views54 pages

06 COMP2150 OCCF Complete

The document outlines the key concepts of Orthodox Canonical Class Form (OCCF) in C++, which includes destructors, copy constructors, and assignment operators. It emphasizes the importance of standardizing object interactions and managing memory effectively through proper use of destructors and copy constructors. Additionally, it contrasts C++ practices with Java, particularly in handling object copying and memory management.

Uploaded by

bihal61989
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views54 pages

06 COMP2150 OCCF Complete

The document outlines the key concepts of Orthodox Canonical Class Form (OCCF) in C++, which includes destructors, copy constructors, and assignment operators. It emphasizes the importance of standardizing object interactions and managing memory effectively through proper use of destructors and copy constructors. Additionally, it contrasts C++ practices with Java, particularly in handling object copying and memory management.

Uploaded by

bihal61989
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 54

COMP 2150

OCCF
WINTER 2024

1
Outline
• What is OCCF?

• Destructors and finalizers

• Pass-by-reference and pass-by-value

• Assignments and copy constructors

2
Simplifying object interaction
• Simple, predictable interaction is one of the hallmarks of
OO

• So far, you've largely thought of this at the ADT level


• "I see a stack, I expect it to act this way"

• The majority of object interaction is well-below this level,


and often interaction you do not even think of
• e.g. what happens when you create an object? When
you throw it away?

• The most basic operations need to be standardized so that


objects can be treated uniformly
3
Orthodox Canonical Class Form
• This means having standards to ensure that the low-
level operations work the same for all objects

• There are numerous guidelines for these (more


associated with C++ than Java).
• Most have one thing in common - you should write a
set of four "standard" methods that you will need for
(almost) every class automatically:

• Orthodox Canonical Class Form (OCCF)

4
Orthodox Canonical Class Form
• The four things that (in most situations) every C++
class should have are:
• a null constructor → you already know this one
• a destructor
• a copy constructor
• an assignment operator

• Because others will expect your classes to have


them, and the basic behaviour of these hugely
changes how your classes interact

5
Orthodox Canonical Class Form
• These four items are relatively straightforward

• But you haven't actually seen (officially) destructors


or copy constructors yet, so we'll talk about them
first
• You've certainly seen assignment, but not the
idea of defining your own assignment operator!
(you can't actually even do this in Java - not using
the actual = operator, anyway)

6
Destructors

7
Destructors/Finalizers
• You're used to using constructors: methods to
perform tasks associated with the creation of an
object
• There are also destructors - methods that perform
tasks associated with the death of an object
• Neither constructors nor destructors actually create
or destroy - they just do housekeeping when those
events happen
• Destructors are not seen as often in Java as in C++,
because?
8
Destructors/Finalizers
• In C++, we need to release dynamically allocated
space ourselves, whereas Java manages this for us

• There are also other useful housekeeping tasks,


beyond just returning memory, though: close files,
close windows, invoke the next application,…

9
Destructors in C++
• Syntax: ~ClassName()

• called automatically when object is deleted (using


delete) (heap-based objects) or removed from
stack (stack-based objects)

• the latter (stack-based) would happen when a


method returns or any other time a variable can
go out of scope

10
Destructors
• In a hierarchy, when an object goes out of scope, its
destructor is called, followed by its parent's (and so
on) – this happens automatically, you don't need to
call the parent destructor yourself

• Note that's just up a hierarchy – a destructor is


not automatically called for objects that are
contained in/linked to this one (e.g. a node and
its data) – this is the type of case you need to
handle yourself in a destructor

11
Destructors
Class hierarchy Destructor call order

Grandparent ~Grandparent()

Parent ~Parent()

Child ~Child()

Child* c = new Child;


delete c;
12
Destructors
• The problem with destructors and a hierarchy is that
they are rarely called directly – they're most often a
side effect

• i.e., if you had a pointer-to-A and did a delete on


this, A's destructor would be called (just a type
match)

• The problem with this is that the pointer-to-A could


hold an instance of any subclass – we want THAT
destructor then

13
Virtual destructors
• If the appropriate subclass' destructor wasn't called,
then we could potentially have memory leaks or
other problems (anything missing from cleanup) –
we need the destructor to be polymorphic

• In C++, this means a virtual destructor, so that when


a pointer is disposed of, the correct destructor for
the type is called first (and then the others up the
hierarchy)

14
Virtual destructors
• C++ reminds you of this by giving you a warning
message if you write a polymorphic (virtual) method
in a hierarchy and don't have a virtual destructor

• Since you intend to use polymorphism, you probably


want that in a destructor too…

15
In Java
• We have an analogous feature in Java, finalizers:
• void finalize(); //comes from Object class

• When is this called? We don't return memory


ourselves…
• called when object garbage-collected
• Which is when?
• Arbitrary - could be milliseconds after, or ages
depending on how much memory we're using
• Which is why these are rare to see in Java – we
can't care that it happens right away

16
Pass-by-value
vs Pass-by-reference
& Copy Constructors

17
Shallow vs deep copy
• There are two ways to do assignment when we are
working with pointers
• we can assign a pointer to point to the same thing
as something else:
• "shallow copy" - it's just copying the pointer

• or we can make the memory pointed to by one


pointer a copy of that pointed to by the other:
• "deep copy" - there are two objects now

• We have all had situations where you needed to do


one and accidentally did the other
18
C++ vs Java
• In Java this is not too big an issue - syntax is obvious
and you know when you need to do a deep copy and
just write code to do it
• e.g. you need to make a deep copy to send to a
method that will alter the object it receives

• In C++, there are standard rules when a deep copy is


done, and we can write a copy constructor to be
called automatically then… so, when?

19
Example – stack based
Person q("Ryley");
Person p = q;
Person r(p);

• Which of these things is not like the other?

20
Example – stack based
Person q("Ryley");
Person p = q;
Person r(p);
• The first is the only one that does not involve taking
data from another object
• The other two are all making copies of another
object, and this is where C++ would call your copy
constructor if you wrote one!
• default will exist if you don't write one; it just might
not do what you want
21
Copy Constructors in C++
• A copy constructor looks just like a constructor, but it
takes a reference parameter rather than a value
parameter
• First, recall what these are - a value parameter is
allocated when the routine is called, and receives a
copy of the argument from the call
• this is the only way of passing parameters in Java
• Whereas a reference parameter is a pointer
managed internally - it refers/points to the argument
from the caller (and thus allows changes to that
argument)
22
Reference parameters
• In C/C++, things are passed by value unless we say
otherwise
• Recall we have a & operator to obtain the address
of something

• We use that & on the parameter to indicate that C++


is to supply the ADDRESS of the argument given by
the caller (i.e. a pointer) instead of a copy of the
data: this is a reference parameter

23
Reference parameters
• C++ will supply the address, not you

• So if I had a method that wanted to alter its integer


parameter, we could have:

void dostuff(int& x) {….}

• Now, how do we use this… I know it's a pointer…

24
Reference parameters
void dostuff(int& x) {….}
• The parameter x is now a reference parameter
• Much like a Java reference variable

• Internally to dostuff, I would use x just like any other


integer: x++ would make x go up by one, for example

• I DON'T need to dereference the pointer - C++ does


this for me
• In other words, it is meant to “hide” pointers from
the user but still have the same effect as pointers
25
Reference parameters
void dostuff(int& x) {….}
• Similarly, when I call dostuff(), I pass an int, not a
pointer – the pointer to the argument is generated
for me by C++

• Think of it as telling C++ to make the object


changeable (like Java does for all objects)

• But keep in mind it is really passing a pointer


underneath and just managing that pointer for you
internally
26
Why reference parameters?
• For practical purposes it is just saying “allow the
argument to be alterable”

• However, we also use these for the same reason that


most objects in Java are passed by reference

• Pointers are small, objects are (usually) big

• …so it saves us a great deal of time and space to


pass a pointer rather than a copy of a big object

27
Why reference parameters?
• The danger is because they are changeable too, we'll
accidentally change it when we don't intend to. But
we can also ensure this doesn't happen:

void dostuff(const int& x) {….}


//with const, it is illegal to try to change x - USE IT!

28
More on const
• C++ also has a really nice safety measure – const can
also be put on a method name
• e.g., void doIt() const;

• this declares that the method does not change any


data members of the object it belongs to (data
members, not parameters!)

• You should use const wherever you can – stops


accidental changes and makes code more readable
(since anybody can immediately see you don't
INTEND to change anything)
29
More on const
• C++ also has a really nice safety measure – const can
also be put on a method name
• e.g., void doIt() const;

• Consequently, it’s good practice to use const with


your accessors (getters)

• it needs to be in the signature of the method in


both the header (.h) and implementation file
(.cpp)

30
Copy constructors
• Now we can look at a copy constructor - it looks like
a constructor except it takes a reference parameter
to another object of the same type

• e.g. a copy constructor for a Book (with members


title and author):

Book::Book (const Book& source) {


title = source.title;
author = source.author;
}

31
Copy constructors
• Now we can look at a copy constructor - it looks like
a constructor except it takes a reference parameter
to another object of the same type

• e.g. a copy constructor for a Book (with members


title and author):
A copy constructor
always takes a
Book::Book (const Book& source) { reference parameter
title = source.title; to the object that must
author = source.author; } be copied

32
Copy constructors
• Now we can look at a copy constructor - it looks like
a constructor except it takes a reference parameter
to another object of the same type

• e.g. a copy constructor for a Book (with members


title and author):
Notice that we don’t
need to dereference
Book::Book (const Book& source) { (no need to use -> to
title = source.title; access the fields!)
author = source.author; } when using a
reference parameter!

33
Copy constructors
Book::Book (const Book& source) {
title = source.title;
author = source.author; }

• const parameter should be used since we're not


modifying the object we're taking the copy from!

• If Book were organized as a subclass of Item, we


would need to call the superclass copy constructor
Book::Book (const Book& source): Item (source) {…}

34
Default copy constructors
• Does a recursive copy of fields

• Every simple field is copied directly (ints, doubles,


POINTERS, etc.)

• Every field that's an object (and not a pointer) has its


copy constructor called recursively (default copy
constructor if you haven't written one)

35
Default copy constructors
• Remember recursive does not mean deep - if I have a
stack-based Node in an object, a copy constructor
will copy that Node (via its copy constructor)
• if I have a pointer-to-node, the pointer will simply
be copied

• So it is not a deep copy by default (makes sense:


deep copies consume time and memory, so don't do
them unless you need to!)

36
Our copy constructor
• Will be called: whenever we make an object that
involves taking data from another object
• Person p = q;

• But there are also two places where we make a copy


of an argument implicitly:
• When passing the object as a value parameter (a
value parameter always makes a copy of its data)
• When returning an object of this type as a result
of a method
37
Our copy constructor
• You can see why we have standards like OCCF –
omitting a deep copy constructor where one is
necessary would drastically change the behaviour of
the object wherever it is used

38
In Java
• Object in java has a method named clone that you
can call via super (super.clone())

• Your class must implement a Cloneable Interface and


override the clone() method

• clone() might throw CloneNotSupportedException, so


you have to throw or handle it as well

39
In Java
class MyClass implements Cloneable {…
public Object clone() throws CloneNotSupportedException {
Object newThing = super.clone();
return newThing;
}
}
• This is a shallow copy; to make it deep you need to
add your own code (i.e. make a new piece of
memory, copy this over deeply and then return it
rather than just calling super.clone() )
• Most of the time, super.clone() is not what you want,
especially if you want a deep copy
40
Assignment Operator

41
Assignment operator
• Assignment is another of the four items in the OCCF;
it's a fairly obvious one - so obvious, you just use =
and don't think about it…
• However, assignments can have pointer semantics
(i.e. make 2 things point to one another) or copy
semantics (copy the value from one to the other)
• For different types of objects, we may want to define
our own special mechanisms outside of what the
language would do by default
• How do we do this?
42
Assignment in C++
• Default behaviour for = on objects (not pointers) is to
copy fields recursively (i.e. exactly what the default
copy constructor does – i.e. not a deep copy)

• However, in C++, we can overload the = operator


(and other operators) just like we can overload a
method name, so we can get any assignment
behaviour we want

• That's why assignment is part of the OCCF for C++

43
Assignment in C++
• Assignment is actually a message sent to the object
on the left of the =, with the object on the right as an
argument

Person p, q;
p = q;
→ is a message sent to p asking it to “set itself using q”

44
Assignment in C++

• So assignment looks mostly like any other method


when we override it:

MyObject& MyObject::operator = (MyObject& right) {


data = right.getData();
//copy over whatever else we want
return (*this);
}

• Assumes that MyObject has a member data, and a


method getData() to access it
45
Assignment in C++
MyObject& MyObject::operator = (MyObject& right) {
data = right.getData();
//copy over whatever else we want
return (*this);
}
• data here is actually this->data (implicitly)

• In other words, the left operand (of the = operator) is


the instance on which we call the assignment
“method” → it’s the “this” object

46
Assignment in C++
MyObject& MyObject::operator = (MyObject& right) {
data = right.getData();
//copy over whatever else we want
return (*this);
}

• One more thing that's very unusual is there's a


reference (&) in the return type - it's actually
modifying its "left" operand and then returns it (the
new version)

47
Assignment in C++
MyObject& MyObject::operator = (MyObject& right) {
data = right.getData();
//copy over whatever else we want
return (*this);
}

• operator = is the assignment operator


• we can also overload other operators so they
make sense for the data types we define (e.g. +,
++, <<, …)

48
Assignment in C++
MyObject& MyObject::operator = (const MyObject& right){
data = right.getData();
//copy over whatever else we want
return (*this);
}

• Advice: make the parameter received (right) const to


protect it from accidental modifications!

49
In Java? Not possible directly
• Java does NOT let us change the behaviour of
operators ourselves

• but you could define a similar assign() method if you


wanted
• that would be a way of getting a similar behavior
indirectly

50
Orthodox Canonical Class Form
• Assignment, the null constructor, the copy
constructor, and the destructor collectively support
many other higher-level operations

• Put another way, if these don't work the way they're


supposed to for your class, many higher level
behaviours won't either

51
Orthodox Canonical Class Form
• Therefore, they are the ones you should consider
first, even if it doesn't look like you'll need them

• This is not so important in Java but it's pretty vital in


C++

• This is what allows your classes to be treated as


interchangeable building blocks by others!

52
OCCF and course work
• Complete OCCF is NOT expected on A2, but you are
expected to write destructors to ensure memory is
properly returned

53
OCCF and course work
• In future assignments, as well as tests and exams, we
will operate under the policy of "do not write these
items unless you actually need them"

• …where "actually need" implies they are currently


needed in the code you have, as opposed to
"might be used for future extensions"

• e.g. a destructor is necessary NOW if you've


allocated any dynamic memory and will use
delete

54

You might also like