COMP2012 Object-Oriented Programming and Data Structures
Topic 2: Object Initialization, Construction and Destruction
Dr. Desmond Tsoi
Department of Computer Science & Engineering The Hong Kong University of Science and Technology Hong Kong SAR, China
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 1 / 63
Class Object Initialization
If all data members of a class are public (so the class is actually a basic struct), they can be initialized when they are created using the brace initializer “{ }”.
class Word {
public:
int frequency;
const char* str;
/* File: public-member-init.cpp */
};
int main() { Word movie = {1, “Titanic”}; }
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 2 / 63
Class Object Initialization ..
What happens if some of data members are private?
1 class Word 2{
/* File: private-member-init.cpp */
3 4 5 6 7 8 9
public:
int frequency;
private:
const char* str;
};
int main() { Word movie = {1, “Titanic”}; }
private-member-init.cpp:9:40: error: could not convert
{1, “Titanic”} from
int main() { Word movie = {1, “Titanic”}; }
^
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 3 / 63
Part I
Constructors
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 4 / 63
Different Types of C++ Constructors
blank CD
default constructor
MP3 to CD
conversion constructor
pirated CD
copy constructor
Memories
I Dreamed a Dream Phantom of the Opera Don’t Cry for me Argen@na
other constructors
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 5 / 63
C++ Constructor Member Functions
Word movie;
Word director = “J. Cameron”;
Word sci_fi(“Avatar”);
Word drama {“Titanic”};
Word *p = new Word(“action”, 1); // General constructor
// Default constructor
// Implicit conversion constructor
// Explicit conversion constructor
// C++11: Explicit conversion constructor
Syntactically, a class constructor is a special member function having the same name as the class.
A constructor must not specify a return type or explicitly returns a value — not even the void type.
A constructor is called whenever an object is created:
object creation
object passed to a function by value
object returned from a function by value
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 6 / 63
Default Initializers for Non-static Data Members (C++11)
class Word /* File: default-initializer.cpp */
{ // Implicitly private members
int frequency {0};
const char* str {nullptr};
};
int main() { Word movie; }
C++11 allows default values for non-static data members of a class.
Nevertheless, C++ supports a more general mechanism for user-defined initialization of class objects through constructor member functions.
During the construction of a non-global object, if its constructor does not initialize a non-static member, it will have the value of its default initializer if it exists, otherwise its value is undefined.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 7 / 63
Default Constructor
class Word {
Default Constructor X::X( ) for Class X
A constructor that can be called with no arguments. /* File: default-constructor.cpp */
private:
int frequency;
char* str;
public:
Word() { frequency = 0; str = nullptr; } // Default constructor
};
int main() {
Word movie; // No arguments => expect default constructor
}
c.f. Variable definition of basic data types: int x; float y;
It is used to create objects with user-defined default values.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 8 / 63
Compiler-Generated Default Constructor
class Word /* File: compiler-default-constructor.cpp */
{ // Implicitly private members
int frequency;
char* str; };
int main() { Word movie; }
If there are no user-defined constructors in the definition of class X,
the compiler will generate the following default constructor for it, X::X() { }
Word::Word() { } only creates a Word object with enough space for its int component and char* component.
The initial values of the data members cannot be trusted.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 9 / 63
Default Constructor: Common Bug
Only when no user-defined constructors are found, will the compiler automatically supply the simple default constructor, X::X(){ }.
1 class Word /* File: default-constructor-bug.cpp */ 2{
3 4 5 6 7
private: int frequency; char* str;
public: Word(const char* s, int k = 0);
};
int main() { Word movie; } // which constructor?
default-constructor-bug.cpp:7:19: error: no matching function for call to Word::Word() int main() { Word movie; } // which constructor?
^~~~~
default-constructor-bug.cpp:4:11: note: candidate: Word::Word(const char*, int) public: Word(const char* s, int k = 0);
^~~~
default-constructor-bug.cpp:4:11: note: candidate expects 2 arguments, 0 provided
default-constructor-bug.cpp:1:7: note: candidate: constexpr Word::Word(const Word&) default-constructor-bug.cpp:1:7: note: candidate expects 1 argument, 0 provided
default-constructor-bug.cpp:1:7: note: candidate: constexpr Word::Word(Word&&) default-constructor-bug.cpp:1:7: note: candidate expects 1 argument, 0 provided
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 10 / 63
Implicit Conversion Constructor(s)
#include
class Word
{
private: int frequency; char* str;
public:
Word(char c)
{ frequency = 1; str = new char[2]; str[0] = c; str[1] = ‘\0’; }
Word(const char* s) // Assumption: s != nullptr
{ frequency = 1; str = new char [strlen(s)+1]; strcpy(str, s); }
};
int main() {
}
// Explicit conversion
// Explicit conversion
// Implicit conversion
A constructor accepting a single argument specifies a conversion from its argument type to the type of its class:
Word(const char*): const char* −→ Word
Word(char): char −→ Word
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 11 / 63
Word movie(“Titanic”);
Word movie2(‘A’);
Word movie3 = ‘B’;
Word director = “James Cameron”; // Implicit conversion
Implicit Conversion Constructor(s) ..
#include
class Word
{
}
int frequency; char* str;
public:
Word(const char* s, int k = 1)
{
frequency = k;
// Still conversion constructor!
str = new char [strlen(s)+1]; strcpy(str, s);
}
};
int main() {
// Explicit conversion
// Explicit conversion
A class may have more than one conversion constructors.
A constructor may have multiple arguments; if all but one argument
have default values, it is still a conversion constructor.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 12 / 63
Word *p = new Word(“action”);
Word movie(“Titanic”);
Word director = “James Cameron”; // Implicit conversion
Implicit Conversion By Surprise
#include
#include
using namespace std;
class Word
{
private:
/* File: implicit-conversion-surprise.cpp */
int frequency; char* str;
public:
Word(char c)
{ frequency = 1; str = new char[2]; str[0] = c; str[1] = ‘\0’;
cout << "call implicit char conversion\n"; }
Word(const char* s)
{ frequency = 1; str = new char [strlen(s)+1]; strcpy(str, s);
cout << "call implicit const char* conversion\n"; }
void print() const { cout << str << " : " << frequency << endl; }
};
void print_word(Word x) { x.print(); }
int main() { print_word("Titanic"); print_word('A'); return 0; }
To disallow perhaps unexpected implicit conversion (c.f. coercion among basic types), add the keyword ‘explicit’ before a conversion constructor.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 13 / 63
Explicit Conversion Constructor(s)
#include
class Word
{
private:
int frequency; char* str;
public:
explicit Word(const char* s)
{ frequency = 1; str = new char [strlen(s)+1]; strcpy(str,s); }
};
int main() {
Word *p = new Word(“action”); // Explicit conversion
Word movie(“Titanic”); // Explicit conversion
Word director = “James Cameron”; // Bug: implicit conversion
}
explicit-conversion-constructor.cpp:15:21: error: conversion
from const char [14] to non-scalar type Word requested
Word director = “James Cameron”; // Bug: implicit conversion
^~~~~~~~~~~~~~~
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 14 / 63
Copy Constructor
#include
#include
using namespace std;
class Word {
/* File: copy-constructor.cpp */
private:
int frequency; char* str;
void set(int f, const char* s)
{ frequency = f; str = new char [strlen(s)+1]; strcpy(str,s); }
public:
Word(const char* s, int k = 1)
{ set(k, s); cout << "conversion\n"; }
Word(const Word& w)
{ set(w.frequency, w.str); cout << "copy\n"; }
};
int main() {
Word movie("Titanic"); // which constructor?
Word song(movie); // which constructor?
Word ship = movie; // which constructor?
Word actress {"Kate"}; // which constructor?
}
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 15 / 63
Copy Constructor ..
Copy Constructor: X::X(const X& ) for Class X
A constructor that has exactly one argument of the same class passed by its const reference.
It is called upon when:
parameter passed to a function by
value.
initialization using the assignment syntax though it actually is not an assignment:
Word x("Star Wars"); Word y = x; object returned by a function by value.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 16 / 63
Return-by-Value ⇒ Copy Constructor
1 #include
2 #include
3 using namespace std;
4 class Word
/* File: return-by-value.cpp */
5{ 6
7
8
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
private:
int frequency; char* str;
void set(int f, const char* s)
{ frequency = f; str = new char [strlen(s)+1]; strcpy(str, s); }
public:
Word(const char* s, int k = 1) { set(k, s); cout << "conversion\n"; }
Word(const Word& w) { set(w.frequency, w.str); cout << "copy\n"; }
void print() const { cout << str << " : " << frequency << endl; }
Word to_upper_case() const
{
Word x(*this);
for (char* p = x.str; *p != '\0'; p++) *p += 'A' - 'a';
return x; }
};
int
main()
{
Word movie("titanic"); movie.print();
Word song = movie.to_upper_case(); song.print();
}
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 17 / 63
Copy Elision and Return Value Optimization
How many calls of the copy constructor do you expect?
Below is the actual output from the previous example:
conversion
titanic : 1
copy
TITANIC : 1
Return Value Optimization (RVO) is a compiler optimization technique which applies copy elision in a return statement.
It omits copy/move operation by constructing a local (temporary) object directly into the function’s return value!
For the example, codes that are supposed to be run by ‘x’ are run directly on ‘song’.
Question: Which line calls the copy constructor?
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 18 / 63
Default Copy Constructor
class Word /* File: default-copy-constructor.cpp */
{
private: ...
public: Word(const char* s, int k = 0) { ... };
};
int main() {
Word movie("Titanic"); // which constructor?
Word song(movie); // which constructor?
Word song = movie; // which constructor?
}
If no copy constructor is defined, the compiler will automatically supply a default copy constructor for it,
X(const X&) { /* memberwise copy */ }
⇒ memberwise assignment (aka copy assignment) by calling the copy constructor of each data member:
copy movie.frequency to song.frequency
copy movie.str to song.str
It works even for array members by copying each array element.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 19 / 63
Default Memberwise Assignment
Objects of basic data types support many operator functions such as +,−,×,/.
C++ allows user-defined types to overload most (not all) operators to re-define the behavior for their objects — operator overloading.
Unless you re-define the assignment operator ‘=’ for a class, the compiler generates the default assignment operator function — memberwise assignment — for it.
Different from the default copy constructor, the default assignment operator= will perform memberwise assignment by calling the assignment operator= of each data member:
song.frequency = movie.frequency song.str = movie.str
Again for array members, each array element is assigned.
Memberwise assignment/copy is usually not what you want when
memory allocation is required for the class members.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 20 / 63
Default Memberwise Assignment With Array Data
#include
#include
using namespace std;
class Word
{
private:
/* File: default-assign-problem1.cpp */
int frequency; char str[100];
void set(int f, const char* s) { frequency = f; strcpy(str, s); }
public:
Word(const char* s, int k = 1)
{ set(k, s); cout << "\nImplicit const char* conversion\n"; }
Word(const Word& w) { set(w.frequency, w.str); cout << "\nCopy\n"; }
void print() const // Also prints the address of object's str array
{ cout << str << " : " << frequency << " ; "
};
int main() {
Word x("rat"); x.print();
Word y = x; y.print();
Word z("cat"); z.print();
z = x; z.print();
// Conversion constructor
// Copy constructor
// Conversion constructor
// Default assignment operator
}
<< reinterpret_cast
#include
using namespace std;
class Word
{
private: int frequency; char* str;
void set(int f, const char* s)
{ frequency = f; str = new char [strlen(s)+1]; strcpy(str, s); }
public:
Word(const char* s, int k = 1)
{ set(k, s); cout << "\nImplicit const char* conversion\n"; }
Word(const Word& w) { set(w.frequency, w.str); cout << "\nCopy\n"; }
void print() const // Also prints the address of object's str array
{ cout << str << " : " << frequency << " ; "
};
int main() {
}
<< reinterpret_cast
#include
using namespace std;
class Word {
/* File: overload-function.cpp */
private:
int frequency; char* str;
public:
void set() const { cout << "Input the string: "; cin >> str; }
void set(int k) { frequency = k; }
void set(char c) { str = new char [2]; str[0] = c; str[1] = ‘\0’; }
void set(const char* s) { str = new char [strlen(s)+1]; strcpy(str, s); }
};
int main() {
Word movie;
movie.set();
// Which constructor?
// Which set function?
}
// Error!
Rm 3553, desmond@ust.hk
COMP2012 (Fall 2020)
29 / 63
Review: Functions with Default Arguments
If a function shows some default behaviors most of the time, and some exceptional behaviors only once awhile, specifying default arguments is a better option than using overloading.
There may be more than one default arguments.
void upload(char* prog, char os = LINUX, char format = TEXT);
Parameters without default values must be declared to the left of those with default arguments. The following is an error:
void upload(char os = LINUX, char* prog, char format = TEXT); A parameter can have its default argument specified only once in a file, usually in the public header file, and not in the function definition. Thus, the following is an error.
class Word // File: word.h
{
… public:
Word(const char* s, int k = 1);
#include “word.h” // File: word.cpp
Word::Word(const char* s, int k = 1)
{
… }
}
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 30 / 63
Part II
Member Initialization List
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 31 / 63
Member Initializer List (MIL)
So far, data members of a class are initialized inside the body of its constructors.
It is actually preferred to initialize them before the constructors’ function body through the member initializer list by calling their own constructors.
It starts after the constructor header but before the opening { .
: member1(expression1), member2(expression2), …
The order of the members in the list doesn’t matter; the actual
execution order is their order in the class declaration.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 32 / 63
Member Initializer List ..
class Word {
private:
char lang;
int freq;
char* str;
/* File: mil-word.h */
public:
Word() : lang(‘E’), freq(0), str(nullptr) { };
/* Or, using the braced initialization syntax as follows
Word() : lang{‘E’}, freq{0}, str{nullptr} { };
*/
Word(const char* s, int f = 1, char g = ‘E’) : lang(g), freq(f)
{ str = new char [strlen(s)+1]; strcpy(str, s); }
void print() const { cout << str << " : " << freq << endl; }
};
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 33 / 63
Member Initializer List ..
Since the MIL calls the constructors of the data member, it works well for data members of user-defined types.
Thus, it is better to perform initialization by MIL than by assignments inside constructors.
Make sure that the corresponding member constructors exist!
class Word_Pair /* File: mil-word-pair.h */
{
private:
Word w1; Word w2;
public:
Word_Pair(const char* s1, const char* s2) : w1(s1,5), w2(s2) { }
void print() const
{
cout << "word1 = "; w1.print();
cout << "word2 = "; w2.print();
}
};
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 34 / 63
Problem If Member Initializer List Is Not Used
class Word_Pair /* File: member-class-init-by-mil.h */
{
private:
Word w1; Word w2;
public:
Word_Pair(const char* s1, const char* s2) : w1(s1,5), w2(s2) { }
};
⇒ w1 and w2 are initialized using the conversion constructor, Word(const char*, int = 1, char = ‘E’).
Word_Pair(const char* x, const char* y) { w1 = x; w2 = y; }
⇒ error-prone because w1 and w2 are initialized by assignment. If the assignment operator function is not appropriately defined, the default memberwise assignment may not be good enough.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 35 / 63
Initialization of const or Reference Members
const or reference members must be initialized using member initializer list if they don’t have default initializers.
c.f. float y; float& z = y; const int x = 123;
#include
using namespace std;
int a = 5;
class Example
{
const int const_m = 3;
int& ref_m = a;
public:
Example() { }
Example(int c, int& r) : const_m(c), ref_m(r) { }
void print() const { cout << const_m << "\t" << ref_m << endl; }
};
int main() {
Example x; x.print();
int b = 55; Example y(10, b); y.print();
}
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 36 / 63
Initialization of const or Reference Members ..
It cannot be done using default arguments.
1 #include
2 using namespace std;
3 class Word
4{
5 6 7 8 9
10 11 12 13 14 15
private:
const char lang; int freq; char* str;
public:
Word() : lang(‘E’), freq(0), str(nullptr) { };
Word(const char* s, int f = 1, char g = ‘E’)
{ str = new char [strlen(s)+1]; strcpy(str, s); }
void print() const
{ cout << str << " : " << freq << endl; }
};
int main() { Word x("hkust"); } mil-const-member-error.cpp:9:5: error: constructor for 'Word' must explicitly initialize the const member 'lang'
Word(const char* s, int f = 1, char g = 'E')
^
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 37 / 63
Delegating Constructor vs. Private Utility Function
#include
#include
using namespace std;
class Word {
/* File: copy-constructor2.cpp */
private:
int frequency; char* str;
void set(int f, const char* s) // Private utility function
{ frequency = f; str = new char [strlen(s)+1]; strcpy(str,s); }
public:
Word(const char* s, int k = 1)
{ set(k, s); cout << "conversion\n"; }
Word(const Word& w)
{ set(w.frequency, w.str); cout << "copy\n"; }
};
In this previous example, since most of the code of the conversion and copy constructors are similar, they are defined with a private utility function set().
May we achieve similar result without defining the latter?
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 38 / 63
Delegating Constructor (C++11)
#include
#include
using namespace std;
class Word {
/* File: delegating-constructor.cpp */
// Modified from copy-constructor.cpp
private:
int frequency; char* str;
public:
Word(const char* s, int f = 1)
{
frequency = f; str = new char [strlen(s)+1]; strcpy(str, s);
cout << "conversion" << endl;
}
Word(const Word& w) : Word(w.str, w.frequency) { cout << "copy" << endl; }
void print() const { cout << str << " : " << frequency << endl; }
};
int main() {
Word movie("Titanic"); movie.print(); // which constructor?
Word song(movie); song.print(); // which constructor?
Word ship = movie; ship.print(); // which constructor?
}
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 39 / 63
Delegating Constructor (C++11)
In this example, the copy constructor, using the member initializer list syntax, delegates the conversion constructor to create an object.
The copy constructor is now a delegating constructor.
Restriction: the delegated constructor must be the only item in the
MIL.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 40 / 63
Part III
Garbage Collection & Destructor
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 41 / 63
Memory Layout of a Running Program
void f() /* File: var.cpp */
{
// x, y are local variables
// on the runtime stack
int x = 4;
Word y("Titanic");
// p is another local variable
// on the runtime stack.
// But the array of 100 int
// that p points to
// is on the heap
int* p = new int [100];
}
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 42 / 63
Memory Usage on the Runtime Stack and Heap
Local variables are constructed (created) when they are defined in a function/block on the run-time stack.
When the function/block terminates, the local variables inside and the call-by-value (CBV) arguments will be destructed (and removed) from the run-time stack.
Both construction and destruction of variables are done automatically by the compiler by calling the appropriate constructors and destructors.
Dynamically allocated memory remains after function/block terminates, and it is the user’s responsibility to return it back to the heap for recycling using delete; otherwise, it will stay until the program finishes.
Garbage is a piece of storage that is part of a running program but there are no more references to it.
Memory leak occurs when there is garbage.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 43 / 63
Destructor
Destructor X::∼X( ) for Class X
The destructor of a class is invoked automatically whenever its object goes
out of (e.g., function/block) scope.
A destructor is a special class member function.
A destructor takes no arguments, and has no return type.
Thus, there can only be one destructor for a class.
If no destructor is defined, the compiler will automatically generate a default destructor which does nothing.
X::∼X( ) { }
The destructor itself does not actually release the object’s memory.
The destructor performs termination housekeeping before the object’s memory is reclaimed by the system.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 44 / 63
Sometimes Default Destructor Is Not Good Enough
void Example() /* File: default-destructor-problem.cpp */
{
Word x("bug", 4);
... }
int main() { Example(); .... }
On return from Example( ), the local Word object “x” of Example( )
is destructed from the run-time stack.
i.e., the storage of (int) x.frequency and (char*) x.str are
released.
Question: How about the memory dynamically allocated for the string, “bug” that x.str points to?
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 45 / 63
User-Defined Destructor
C++ supports a general mechanism for user-defined destruction of objects through destructor member function.
Usually needed when there are pointer members pointing to memory dynamically allocated by constructor(s) of the class.
#include
class Word
{
private:
int frequency; char* str;
public:
Word() : frequency(0), str(nullptr) { };
Word(const char* s, int k = 0): frequency(k)
{ str = new char [strlen(s)+1]; strcpy(str, s); }
̃Word() { delete [] str; }
};
int main()
{
Word* p = new Word(“Titanic”);
Word* x = new Word [5];
}
delete p;
delete [] x;
return 0;
// Destruct a single object
// Destruct an array of objects
Rm 3553, desmond@ust.hk
COMP2012 (Fall 2020) 46 / 63
Bug: Default Memberwise Assignment
1 #include
3 class Word
4{
5 6 7 8 9
10 11 12 13 14 15 16 17
private:
int frequency; char* str;
public:
Word() : frequency(0), str(nullptr) { }
Word(const char* s, int k = 0): frequency(k)
{ str = new char [strlen(s)+1]; strcpy(str, s); }
̃Word() { delete [] str; }
};
void Bug(Word& x) { Word bug(“bug”, 4); x = bug; }
int main() { Word movie(“Titanic”); Bug(movie); return 0; }
Question: How many bugs are there?
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 47 / 63
Summary: Compiler-generated Member Functions
Unless you define the following, they will be implicitly generated by the compiler for you:
1. default constructor
(but only if you don’t define other constructors)
2. default copy constructor
3. default (copy) assignment operator function
4. default move constructor (C++11)
5. default move assignment operator function (C++11)
6. default destructor
C++11 allows you to explicitly generate or not generate them: to generate: = default;
not to generate: = delete;
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 48 / 63
Example: = default; = delete;
#include
#include
using namespace std;
class Word {
/* File: default-delete.cpp */
private:
int frequency {0}; char* str {nullptr};
public:
Word() = default; // Still want the simple default constructor
Word(const Word& w) = delete; // Words can’t be copied
Word(const char* s, int k) : frequency(k)
{ str = new char [strlen(s)+1]; strcpy(str, s); }
void print() const
{ cout << ((str == nullptr) ? "not-a-word" : str)
<< " : " << frequency << endl; }
};
int main() {
Word x; x.print();
Word y("good", 3); y.print();
Word z(y); // Error: call to deleted constructor of ’Word’
}
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 49 / 63
Part IV
Order of Construction & Destruction
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 50 / 63
“Has” Relationship
When an object A has an object B as a data member, we say “A has a B.”
It is easy to see which objects have other objects. All you need to do is to look at the class definition.
/* File: example-has.h */
class B { ... };
class A {
private:
B my_b;
public:
// Declaration of public members or functions
};
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 51 / 63
Cons/Destruction Order: Postoffice Has a Clock
class Clock /* File: postoffice1.h */
{
public:
Clock() { cout << "Clock Constructor\n"; }
̃Clock() { cout << "Clock Destructor\n"; }
};
class Postoffice
{
Clock clock;
public:
Postoffice() { cout << "Postoffice Constructor\n"; }
̃Postoffice() { cout << "Postoffice Destructor\n"; }
};
#include
using namespace std;
#include “postoffice1.h”
int main()
{
cout << "Beginning of main\n";
Postoffice x;
cout << "End of main\n";
}
Beginning of main
Clock Constructor
Postoffice Constructor
End of main
Postoffice Destructor
Clock Destructor
Rm 3553, desmond@ust.hk
COMP2012 (Fall 2020)
52 / 63
Cons/Destruction Order: Postoffice Has a Clock ..
When an object is constructed, all its data members are constructed first.
The order of destruction is the exact opposite of the order of construction: The Clock constructor is called before the Postoffice constructor code; but, the Clock destructor is called after the Postoffice destructor code.
As always, construction of data member objects is done by calling their appropriate constructors.
If you do not do this explicitly then their default constructors are assumed. Make sure they exist! That is,
Postoffice::Postoffice() { }
is equivalent to,
Postoffice::Postoffice() : clock() { }
Or, you may do this explicitly by calling their appropriate constructors
using the member initialization list syntax.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 53 / 63
Cons/Destruction Order: Postoffice “Owns” a Clock
class Clock /* File: postoffice2.h */
{
public:
Clock() { cout << "Clock Constructor\n"; }
̃Clock() { cout << "Clock Destructor\n"; }
};
class Postoffice
{
Clock* clock;
public:
Postoffice()
{ clock = new Clock; cout << "Postoffice Constructor\n"; }
̃Postoffice() { cout << "Postoffice Destructor\n"; }
};
/* File: postoffice2.cpp */ #include
using namespace std; #include “postoffice2.h” int main()
{
}
Beginning of main
Clock Constructor
Postoffice Constructor
End of main
Postoffice Destructor
cout << "Beginning of main\n";
Postoffice x;
cout << "End of main\n";
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020)
54 / 63
Cons/Destruction Order: Postoffice “Owns” a Clock ..
Now the Postoffice “owns” a Clock.
This is the terminology used in OOP. If A “owns” B, A only has a
pointer pointing to B.
The Clock object is constructed in the Postoffice constructor, but
it is never destructed, since we have not implemented that. Remember that objects on the heap are never destructed
automatically, so we have just created a memory leak.
When object A owns object B, A is responsible for B’s destruction.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 55 / 63
Cons/Destruction Order: Postoffice “Owns” a Clock ...
class Clock /* File: postoffice3.h */
{
public:
Clock() { cout << "Clock Constructor\n"; }
̃Clock() { cout << "Clock Destructor\n"; }
};
class Postoffice
{
Clock* clock;
public:
Postoffice()
{ clock = new Clock; cout << "Postoffice Constructor\n"; }
̃Postoffice()
{ cout << "Postoffice Destructor\n"; delete clock; }
};
/* File: postoffice3.cpp */ #include
using namespace std; #include “postoffice3.h” int main()
{
}
Beginning of main
Clock Constructor
Postoffice Constructor
End of main
Postoffice Destructor
Clock Destructor
cout << "Beginning of main\n";
Postoffice x;
cout << "End of main\n";
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020)
56 / 63
Cons/Destruction Order: Postoffice Has Clock + Room
class Clock /* File: postoffice4.h */
{
/* File: postoffice4.cpp */ #include
using namespace std; #include “postoffice4.h” int main()
{
cout << "Beginning of main\n";
Postoffice x;
cout << "End of main\n";
}
private: int HHMM;
public:
// hour, minute
Clock() : HHMM(0)
{ cout << "Clock Constructor\n"; }
̃Clock() { cout << "Clock Destructor\n"; }
};
class Room {
public:
Room() { cout << "Room Constructor\n"; }
̃Room() { cout << "Room Destructor\n"; }
};
class Postoffice
{
private:
Room room; Clock clock;
public:
Postoffice()
Beginning of main
Room Constructor
Clock Constructor
Postoffice Constructor
End of main
Postoffice Destructor
Clock Destructor
Room Destructor
};
{ cout << "Postoffice Constructor\n"; }
̃Postoffice()
{ cout << "Postoffice Destructor\n"; } Rm 3553, desmond@ust.hk COMP2012 (Fall 2020)
†† Note that the 2 data members, Clock and Room are constructed first, in the order that they appear in the Postoffice class.
57 / 63
Cons/Destruction Order: Postoffice Moves Clock to Room
class Clock /* File: postoffice5.h */
{
public:
Clock() { cout << "Clock Constructor\n"; }
̃Clock() { cout << "Clock Destructor\n"; }
};
class Room {
private:
Clock clock;
public:
Room() { cout << "Room Constructor\n"; }
̃Room() { cout << "Room Destructor\n"; }
};
class Postoffice
{
private:
Room room;
public:
Postoffice()
/* File: postoffice5.cpp */ #include
using namespace std; #include “postoffice5.h” int main()
{
cout << "Beginning of main\n";
Postoffice x;
cout << "End of main\n";
};
{ cout << "Postoffice Constructor\n"; }
̃Postoffice()
{ cout << "Postoffice Destructor\n"; } Rm 3553, desmond@ust.hk COMP2012 (Fall 2020)
}
Beginning of main
Clock Constructor
Room Constructor
Postoffice Constructor
End of main
Postoffice Destructor
Room Destructor
Clock Destructor
58 / 63
Cons/Destruction Order: Postoffice w/ a Temporary Clock
class Clock { /* File: postoffice6.h */
private: int HHMM;
public:
Clock() : HHMM(0) { cout << "Clock Constructor\n"; }
Clock(int hhmm) : HHMM(hhmm)
{ cout << "Clock Constructor at " << HHMM << endl; }
̃Clock() { cout << "Clock Destructor at " << HHMM << endl; }
};
class Postoffice {
private: Clock clock;
public:
Postoffice()
{ cout << "Postoffice Constructor\n"; clock = Clock(800); }
̃Postoffice() { cout << "Postoffice Destructor\n"; }
};
#include
#include “postoffice6.h”
int main() {
cout << "Beginning of main\n";
Postoffice x;
cout << "End of main\n";
}
Rm 3553, desmond@ust.hk
COMP2012 (Fall 2020)
59 / 63
Cons/Destruction Order: Postoffice w/ a Temp Clock ..
Beginning of main
Clock Constructor
Postoffice Constructor
Clock Constructor at 800
Clock Destructor at 800
End of main
Postoffice Destructor
Clock Destructor at 800
Here a temporary clock object is created by Clock(800). Like a ghost, it is created and destroyed behind the scene.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 60 / 63
Default Member Initialization and Order of Construction
#include
using namespace std;
class A {
int a;
public:
/* file: default-member-init.cpp */
A(int z) : a(z) { cout << "call A's constructor: " << a << endl; }
̃A() { cout << "call A's destructor: " << a << endl; }
int get() const { return a; }
};
class B {
int b1 = 999;
A b2 = 10;
A b3 {100};
// Remember: can't initialize by ( )
// Call A's conversion constructor
// Call A's conversion constructor
public:
B() { cout << "call B's default constructor" << endl; }
̃B() { cout << "call B's destructor: " << b1 << "\t"
<< b2.get() << "\t" << b3.get() << endl; }
};
int main() { B x; return 0; }
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 61 / 63
Summary
When an object is constructed, its data members are constructed first.
When the object is destructed, the data members are destructed after the destructor code of the object has been executed.
When object A owns other objects, remember to destruct them as well in A’s destructor.
By default, the default constructor is used for the data members.
We can use a different constructor for the data members by using member initialization list — the “colon syntax”.
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 62 / 63
That’s all! Any questions?
Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 63 / 63