Constructors, Destructors, Operator Overloading CS 1037 Fundamentals of Computer Science II.
-
date post
21-Dec-2015 -
Category
Documents
-
view
226 -
download
2
Transcript of Constructors, Destructors, Operator Overloading CS 1037 Fundamentals of Computer Science II.
Constructors, Destructors, Operator Overloading
CS 1037 Fundamentals of Computer Science II
2
Some Special Functions...
A. Constructors (to set up an object’s state)
Destructors (to ‘tear down’ an object)
B. Operator Overloading (extending C++)
struct sound { int size; double* samples; sound(); // constructor declaration ~sound(); // destructor declaration};
void operator*=(sound& s, double ratio);
sound make_me_louder;make_me_louder *= 2.0; // now C++ knows what this means
3
Part A. Constructors, Destructorsstruct point { int x, y; point(); // default-constructor declaration};
point::point() { // default-constructor definition x = 0; y = 0;}
4
Why Constructors?
• Variables/objects start with random data
• Uninitialized variables huge source of bugs– most objects need initialization before use (dynarray)– some languages force initialization before use (C#)
• Constructors allow consistent, automatic initialization of member variables in C++
void main() { point pos; // uninitialised! draw_circle(pos.x,pos.y);}
pos???? ????x y
5
Defining a Constructor
• Just define a member function with the same name as the struct/class itselfstruct point { int x, y; point(); // default-constructor declaration};
point::point() { // default-constructor definition x = 0; y = 0;}
void main() { point pos; // call the default-constructor draw_circle(pos.x,pos.y); // draws at (0,0)}
void point();void point::point() {
Omit the void
6
Defining a Constructor
• Constructors can also take arguments! Allows per-object initializationstruct point { int x, y; point(int ax, int ay); // constructor declaration};
point::point(int ax, int ay) { // constructor definition x = ax; y = ay;}
void main() { point pos(0,0); // call the constructor draw_circle(pos.x,pos.y); }
void point();void point::point() {
Omit the void
7
A Small Price to Pay
• If constructors defined, they become the only way to initialize (no initializer-lists)// if just point(int,int) constructor...void main() { point p; // ERROR point q(5,10); // OK point r = {5,10}; // ERROR (*)}
(*) This will work in new versions of C++ circa 2011; see Stroustrup’s comments: http://www2.research.att.com/~bs/C++0xFAQ.html#init-list
8
Multiple Constructors
• Provide whatever makes sense for typestruct point { int x, y; point(); // default-constructor point(int ax, int ay); // a convenient constructor};
point::point() { x = y = 0; }point::point(int ax, int ay) { x = ax; y = ay; }
void main() { point p; // call default-constructor point q(5,10); // call constructor point r(q); // call copy-constructor}
9
Members with Constructorsstruct rectangle { point a,b; rectangle(); // default-constructor rectangle(point pa, point pb); // constructor rectangle(int l, int t, int r, int b); // constructor};
rectangle::rectangle(){ // calls default-constructor point() on a,b}
rectangle::rectangle(point pa, point pb): a(pa), b(pb) // calls copy-constructor on a,b{}
rectangle::rectangle(int l, int t, int r, int b): a(l,t), b(r,b) // calls constructor point(int,int) on a,b{}
10
Members with Constructorsstruct rectangle { point a,b; rectangle(); rectangle(point pa, point pb); rectangle(int l, int t, int r, int b);};
void main() { point p(0,0), q(40,80); rectangle r1; // r1 = { {0,0}, {0,0} } rectangle r2(p,q); // r2 = { {0,0}, {40,80} } rectangle r3(0,0,40,80); // r3 = { {0,0}, {40,80} } rectangle r4(r3); // r4 = { {0,0}, {40,80} }
rectangle r5(point(0,0),point(40,80)); // like r2}
11
Init-Function vs Constructor
struct sound { int size; double* samples;};
// default sound is no soundvoid init_sound(sound& s) { s.size = 0; s.samples = 0;}
void main() { sound snd; init_sound(snd); play_sound(snd);}
struct sound { int size; double* samples; sound();};
sound::sound() { size = 0; samples = 0;}
void main() { sound snd; play_sound(snd);}
==
manual initialization via function automatic initialization
Manual initialization easily forgotten: a classic source of bugs!
12
Destructors
• Constructors handle ‘birth’ of an object• Destructors handle deeaath
• Special member function that gets called automatically when variable/object dies
void main() { sound snd; // snd is born!!} // snd's scope ended... gone, forgotten, DEAD
struct sound { int size; double* samples; sound(); // constructors must be named 'sound' ~sound(); // destructor must be named '~sound'};
13
Why Destructors?• Sophisticated objects need to release
internal resources “clean up after themselves”struct sound { int size; double* samples;};
// de-allocate samples arrayvoid destruct_sound(sound& s) { delete[] s.samples;}
void main() { sound beep; load_sound(beep,"beep.mp3"); ... destruct_sound(beep);}
struct sound { int size; double* samples; ~sound();};
sound::~sound() { delete[] samples;}
void main() { sound beep; load_sound(beep,"beep.mp3"); ...}
==
struct sound { int size; double* samples;};
// de-allocate samples arrayvoid destruct_sound(sound& s) { delete[] s.samples;}
void main() { sound beep; load_sound(beep,"beep.mp3"); ... destruct_sound(beep);}
14
Real Life™ Example
• Destructors can be complicated– e.g. destructor for a real linked-list* class:slist::~slist() { node* n = m_head; while (n != (node*)&m_head) { // loop over list nodes node* prev = n; n = n->next; delete prev; // delete each list item }}
*Not necessarily same destructor used in CS1037 linked list
15
More Examplesstruct noisy { noisy(); ~noisy();};
noisy::noisy() { cout << "HELLO!" << endl; }noisy::~noisy() { cout << "GOODBYE!" << endl; }
void main() { noisy a,b;}
16
More Examples – Arrays
void main() { noisy* ptr = new noisy[3];}
void main() { noisy* ptr = new noisy[3]; delete ptr;}
void main() { noisy* ptr = new noisy[3]; delete[] ptr;}
CORRECT
MEMORY LEAK?
MEMORY LEAK
17
More Examples – Scope
• ‘Noisy’ constructors & destructors good way to teach yourself about variables & their scope.
void main() { noisy a; for (int i = 0; i < 2; ++i) { noisy b,c; }}
You should be able to describe which variable printed which line!
18
More Examples – Arguments
• Constructor for b didn’t print! Why?– b not initialized by default-constructor noisy()– b initialized by copy-constructor, as copy of a
void func(noisy b) { noisy c;}
void main() { noisy a; func(a);}
Wait... what???
19
With Copy-Constructor
struct noisy { noisy(const noisy& src); // copy-constructor noisy(); // default-constructor ("HELLO!") ~noisy(); // destructor (“GOODBYE!”)};
noisy::noisy(const noisy& src) { cout << "HELLO! (COPIED)" << endl; // custom definition}
void func(noisy b) { noisy c;}
void main() { noisy a; func(a);}
Aahh, that’s better
20
More Examples – Scope
void main() { noisy a; noisy b(a); noisy c = a; c = b;}
int x = 5;
void main() { if (x == 5) { noisy a; } else { noisy b,c,d,e; }}
21
Copy-Constructors
• When variable is initialized from value of same type, it has been copy-constructed
• By default, every type has the ‘obvious’ copy-constructor:
point a(5,7); // a is CONSTRUCTED via (x,y) argumentspoint b(a); // b is COPY-CONSTRUCTED from apoint c = a; // c is COPY-CONSTRUCTED from ac = b; // c is COPIED from b
same as point c(a);
whatever a;whatever b(a); // b initialized to byte-by-byte copy of a
22
• Default copy-constructor does ‘raw’ copy
• Raw copy dangerous for some types!
b
Copy-Constructors
point a(5,7);point b(a);
a
...... 00
05
00
07
00
05
00
07
0016
0017
0018
0019
001a
001b
001c
001d
001e
001f
0020
0021
struct sound { int size; double* samples; ~sound(); // delete[] samples};
sound a = ...; // set to whateversound b(a); // UH-OH!
b4
size samples
a4 0.0
0.00.00.0
Who gets to delete[] the array of samples?
23
Defining a Copy-Constructor
• Add constructor with a specific argument:
• Example:
struct name { name(const name& src); // copy-constructor declaration};
name::name(const name& src) { ... // copy-constructor definition}
if you don’t know what const means, don’t worry – just remember to put it!
point::point(const point& src) { x = src.x; y = src.y;}
This copy-constructor does byte-by-byte copy, same asdefault copy-constructor!
24
Why Copy-Constructor?
• Because some objects must be copied very carefully!sound::sound(const sound& src) { size = src.size; samples = new double[size]; for (int i = 0; i < size; ++i) samples[i] = src.samples[i];}
sound a = ...;sound b(a); // NO PROBLEM!
b4
size samples
a4 0.0
0.00.00.0
0.00.00.00.0
25
Problem: Need Copy-Operator Too
• Besides copy-constructor, there’s another way to (accidentally) raw-copy!sound a = ...;sound b(a); // OK, use smart copy-constructorb = a; // UH-OH! copy-operator still stupid!
b4
size samples
a4 0.0
0.00.00.0
0.00.00.00.0
back in same problematic situation!
plus memory leak!
26
Defining a Copy-Operator
• Copy-constructor and copy-operator are almost always defined together
• Key difference: copy-operator must assume object already initialized!
void sound::operator=(const sound& src) { delete[] samples; // delete old array // paste code from copy-constructor}
sound a = ...;sound b(a); // COPY-CONSTRUCTORa.samples[0] = 5.0;b = a; // COPY-OPERATOR
b4
size samples
a4 5.0
0.00.00.0
5.00.00.00.0
first copy of a
(deleted)
27
Defining a Copy-Operator
• Add member function with special name and one specific argument:struct name { void operator=(const name& src); // declaration};
void name::operator=(const name& src) { ... // definition}
void point::operator=(const point& src) { x = src.x; y = src.y;}
This copy-operatoris same as default copy-operator!
28
Careful Copying – Sound Exampleclass sound { int m_size; double* m_samples; void copy(const sound& src);public: sound(char* filename); sound(const sound& src); ~sound(); void operator=(const sound& src);};
sound gaga("pokerface.mp3"); // constructor calledsound radiohead("creep.mp3"); // constructor calledsound music(gaga); // copy-constructor calledmusic = radiohead; // copy-operator (whew!)
sound::sound(const sound& src) { copy(src);}
void sound::operator=(const sound& src) { delete[] m_samples; copy(src);}
void sound::copy(const sound& src) { m_size = src.m_size; m_samples = new double[m_size]; for (int i = 0; i < m_size; ++i) m_samples[i] = src.m_samples[i];}
music4
size samples
5.00.00.00.0
29
Careful Copying – String Exampleclass string { int m_size; char* m_chars; void copy(const string& src);public: string(char* src); // (exercise) string(const string& src); ~string(); void operator=(const string& src);};
string republican("Bush"); // constructor calledstring democrat("Obama"); // constructor calledstring prez(republican); // copy-constructor calledprez = democrat; // copy-operator (whew!)
string::string(const string& src) { copy(src);}
void string::operator=(const string& src) { delete[] m_chars; copy(src);}
void string::copy(const string& src) { m_size = src.m_size; m_chars = new char[m_size+1]; for (int i = 0; i <= m_size; ++i) m_chars[i] = src.m_chars[i];}
prez4
size chars
'B''u''s''h'0zero at end of strings
called “null terminator”
30
Constructors & Implicit Conversion
• How does code like this actually work?
• A constructor like string(char*) gives compiler a way to implicitly convert!
void print(string message) { ...}
void main() { char* str = "hello"; print(str); // str is not 'string', but works anyway!}
print(str); // compiler constructs 'message' from str
31
Constructors & Implicit Conversion
• How does code like this actually work?
message 5
'h' 'e' 'l' 'l' 'o' 0
main
globals
str
void print(string message) { ...}
void main() { char* str = "hello"; print(str); // str is not 'string', but works anyway!}
call stack
'h' 'e' 'l' 'l' 'o' 0
heap
CPU
allocated and initialized by string(char*) constructor
global data, part of .exe file
32
Constructors & Implicit Conversion
• And what actually goes on here?
• Compiler uses constructor to convert!– looks for conversions to do what you asked
void string::operator=(const string& src) { delete[] m_chars; copy(src);}
void main() { string prez("Bush"); prez = "Obama"; // right-side type char*, not string!}
prez = string("Obama"); // compiler secretly does this!
a temporary unnamed string object, constructed from string(char*)
33
Constructors & Implicit Conversion
5
'B' 'u' 's' 'h' 0
main
globals
prez
void string::operator=(const string& src) { delete[] m_chars; copy(src);}
void main() { string prez("Bush"); prez = "Obama"; }
operator=
src
4
'O' 'b' 'a' 'm' 0'a'
this
'O' 'b' 'a' 'm' 0'a'
'B' 'u' 's' 'h' 0
unnamed
34
“Shallow Copy” vs “Deep Copy”
• Copying raw bytes is ‘shallow’ (the default)
• Duplicating entire structure is ‘deep’sound a = ...;sound b = a; // should b be independent copy?
b4
size samples
a4 0.0
0.00.00.0
0.00.00.00.0
b4
size samples
a4 0.0
0.00.00.0
shallow(danger of accidental sharing)
deep(independent copies)
35
Part B. Operator Overloadingpoint a,b;point c = a+b; // add two points
36
What is Operator Overloading?
• operator+(int,int) is built-in
• operator+(point,point) is not
int pos = 1;int vel = 3;pos = pos + vel; // update 1D position
point pos = {1,0};point vel = {3,0};pos = pos + vel; // update 2D position (ERROR)
warning: #include <iostream> changes error message to something crazy
37
What is Operator Overloading?
• We can define what point+point means point operator+(point a, point b) { // Oh, so that's how point p = { a.x+b.x, a.y+b.y }; // I should add two return p; // point variables...}
void main() { point pos = {1,0}; point vel = {3,0}; pos = pos + vel; // OK, pos = (4,0)}
result of adding two points should probably be another point!
38
Why Operator Overloading?
• Useful for strings...
• Useful for mathematics...
• Useful for iterators...
• Useful for array-like data structures...
string username = "yli983";string email = username + "@uwo.ca";
vec3 r = (A*x – b); // residualsdouble error = r*r; // dot product
operator+(string,char*)
operator*(mat3x3,vec3)operator-(vec3,vec3)operator*(vec3,vec3)
for (iter i = first; i != last; ++i) ...
iter::operator++()
dynarray<double> samples;samples[0] = -1.7; dynarray::operator[](int)
(these are not complete operator declarations; just to illustrate their form)
39
Two Ways to Define an Operator
• As global function:
• As member function:
• Same code, but member functions have access to private data; globals do not
point operator+(point a, point b) { point p = { a.x+b.x, a.y+b.y }; return p;}
point point::operator+(point b) { point p = { x+b.x, y+b.y }; return p;}
40
Operators and Pass-By-Reference
• If arguments are copies, can be expensivestring operator+(string a, string b) { string s(a); s.append(b); return s;}
b 5operator+
'O' 'b' 'a' 'm' 'a' 0
CPU
a 6 'B' 'a' 'r' 'a' 'c' 0'k'
5 'O' 'b' 'a' 'm' 'a' 0
6 'B' 'a' 'r' 'a' 'c' 0'k'
s 11 'B' 'a' 'r' 'a' 'c' 'k' 'O' 'b' 'a' 'm' 'a' 0
copies of input strings
41
Operators and Pass-By-Reference
• So, use pass-by-reference instead!string operator+(const string& a, const string& b) { string s(a); s.append(b); return s;}
boperator+
CPU
a
5 'O' 'b' 'a' 'm' 'a' 0
6 'B' 'a' 'r' 'a' 'c' 0'k'
s 11 'B' 'a' 'r' 'a' 'c' 'k' 'O' 'b' 'a' 'm' 'a' 0
42
Examplestruct cyclic_int { int value; int max_value; void operator++();};
void cyclic_int::operator++() { if (++value >= max_value) // wrap back to zero value = 0;}
void main() { cyclic_int minutes = {59,60}; ++minutes; // ++ goes from 59 to 0}
43
Concept Checklist
• You should understand meaning of... globals, call stack, heap variable scope pointer reference static array dynamic array struct pass-by-value pass-by-reference
class member variable member function information hiding constructors copy constructor/operator destructor shallow copy, deep copy overloaded operator
44
Comment on Naming Conventions
Two major conventions:1. lower_case
2. CamelCase
struct user_defined_type { void member_function(); int m_member_variable;};user_defined_type variable_name;
Read thrilling history of CamelCase at http://en.wikipedia.org/wiki/Camelcase
struct UserDefinedType { void memberFunction(); int mMemberVariable;};UserDefinedType variableName;
45
Comment on Naming Conventions
Notes are in lower_case for three reasons:1. Standard C++ libraries use it (STL) 2. I recently switched to it (after 12 years)3. The most reliable way to parse code is
by context, not names; don’t let your brain learn to rely on wrong clues
aaa bbb(ccc ddd) { // brain should be able to infer return ddd.eee(); // a lot from usage/context}