程序代写代做代考 clock data structure compiler c++ COMP2012 Object-Oriented Programming and Data Structures

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 to Word
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 /* File: implicit-conversion-constructor.cpp */
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 /* File: conversion-constructor-default-arg.cpp */
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 /* File: explicit-conversion-constructor.cpp */
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(str) << endl; } Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 21 / 63 Default Memberwise Assignment With Array Data .. Implicit const char* conversion rat : 1 ; 0x7fff5cd2e5d4 Copy rat : 1 ; 0x7fff5cd2e56c Implicit const char* conversion cat : 1 ; 0x7fff5cd2e504 rat : 1 ; 0x7fff5cd2e504 Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 22 / 63 Default Memberwise Assignment With Pointer Data #include /* File: default-assign-problem2.cpp */
#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(str) << endl; } Word x("rat"); x.print(); // Conversion constructor Word y = x; y.print(); // Copy constructor Word z("cat", 2); z.print(); // Conversion constructor z = x; z.print(); // Default assignment operator Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 23 / 63 Default Memberwise Assignment With Pointer Data .. Implicit const char* conversion rat : 1 ; 0x7fc7dbd039c0 Copy rat : 1 ; 0x7fc7dbd039d0 Implicit const char* conversion cat : 2 ; 0x7fc7dbd039e0 rat : 1 ; 0x7fc7dbd039c0 Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 24 / 63 Problem With Default Memberwise Assignment Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 25 / 63 Constructor: Quiz How are class initializations done in the following statements? 1. Word nothing; 2. Word dream grade(’A’); 3. Word major {“COMP”}; 4. Word hkust = “hkust”; 5. Word exchange to(hkust); 6. Word grade = dream grade; 7. Word grade {dream grade}; Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 26 / 63 Uniform Initialization Using {} Initializers Again In general, initializations may be done using ( ), =, or { } int x(1); int y = 2; int z {3}; The bracked initialization syntax helps avoid some misleading syntax from the other two kinds: 1. when = doesn’t really mean assignment! Word word1 = word2; // What is this? 2. when ( ) doesn’t really mean calling the default constructor! Word w(); // What is this? In both cases, braced initialization works fine: Word word1 { word2 }; Word w {}; When a class member of user-defined types is initialized, its corresponding constructor will be called. () initializer cannot be used to do default initialization of non-static class data members. Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 27 / 63 Constructors and Function Overloading Overloading allows programmers to use the same name for functions that do similar things but with different input arguments. Constructors are often overloaded. class Word { private: int frequency; char* str; /* File: overload-constructor.cpp */ public: Word(); // Default constructor Word(const char* s, int k = 1); // Conversion constructor Word(const Word& w); // Copy constructor }; Rm 3553, desmond@ust.hk COMP2012 (Fall 2020) 28 / 63 Review: Function Overloading .. In general, function names can be overloaded in C++. Actually, operators are often overloaded. e.g., What is the type of the operands for “+”? #include
#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 /* File: mil-const-ref.cpp */
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 /* File: mil-const-member-error.cpp */
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 /* File: destructor.cpp */
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 /* File: default-assign-bug.cpp */ 2
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 /* File postoffice1.cpp */
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 /* File: postoffice6.cpp */ using namespace std;
#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