(OOP) Construction and Destruction
Class object initialization
If all data members of a class are public
, they can be initialized when they are created using {}
.
But what if some of the data members are private
?
-> use CONSTRUCTORS for initialization! This is because int main()
function does not have access to private data members.
Constructors
Constructor is a special kind of class member function to initialize an object.
- It must not specify a return type or explicitly return a value.
- It is called whenever an object is created.
There are 4 different kinds of constructors:
- Default constructor
- Conversion coustructor
- Copy constructor
- Other constructors
Default constructor
Default constructor can be called with no arguments
.
It is used to create objects with user-defined default
values.
class Word
{
private:
int frequency;
char* str;
public:
Word() { frequency = 0; str = nullptr; } // Default constructor
};
int main()
{
Word movie; // No arguments -> expect default constructor
}
If there are no user-defined constructors in the definition of class X
, the compiler will generate the following default constructor:
X::X() {}
-> this creates an object X with enough space for its data members.
class Word
{
// implicitly private members
int frequency;
char* str;
// Since no default constructors, the compiler will generate:
Word::Word() {} // creates a Word object with enough space for its int component and char* component.
};
int main() { Word movie; }
Conversion constructor
Conversion constructor accepts a single argument
. It specifies a conversion from its argument type to the type of its class.
- A class may have more than one conversion constructor.
- A constructor may have multiple arguments; if all but
one
argument havedefault values
, it is still a conversion constructor.
#include <cstring>
class Word
{
int frequency; char* str;
public:
Word(const char* s, int k = 1) // still conversion constructor, since it can be run with single argument.
{
frequency = k;
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"; // Implicit conversion
// For implicit conversion, conversion of its argument type to the type of its class
Word(const char*): const char* -> Word
}
- To disallow unexpected implicit conversion, add the keyword
explicit
before a conversion constructor.class Word { public: explicit Word(const char* s, int k = 1) { ... } }
Copy constructor
Copy constructor has exactly one argument
of the same class
passed by its const reference
.
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 Word& w) // copy constructor
{ set(w.frequency, w.str); }
};
int main()
{
Word song(movie); // parameter passed by value to function
Word ship = movie; // object returned by value by function
}
Return value optimization
is a compiler optimization technique which applies copy elision
in a return statement.
This means that it omits copy/move
by constructing a local temporary object directly into the function’s return value.
Default copy constructor
If no copy constructor: the compiler will automatically supply it a default copy constructor
.
X(const X&) { /* memberwise copy */ }
memberwise copy
: calls the copy constructor of each data member.
e.g. for Word song {movie}:
- copy movie.frequency to song.frequency
- copy movie.str to song.str
Default memberwise assignment
The default assignment operator ‘=’ will perform memberwise assignment to each data member:
- song.frequency = movie.frequency
- song.str = movie.str
Memberwise copy와 memberwise assignment의 차이:
int main()
{
Word x("rat"); // 1. conversion constructor
Word y = x; // 2. copy constructor
Word z("cat", 2); // 3. conversion constructor
z = x; // 4. default assignment operator
}
- 1과 2같은 경우에선
memberwise copy
가 일어난다. x라는 Word object가 형성이 되었고, y라는 새로운 object를 형성하는 동시에 x의 정보를copy
한다. - 3과 4같은 경우는
memberwise assignment
가 일어난다. z라는 Word object가 형성이 되었고, 이미 형성되어있는 z에 x의 정보를assign
한다.
Constructors and function overloading
Constructors are often overloaded.
Functions with default arguments
Parameters without default values must be declared to the left
of those with default arguments.
Member Initializer List (MIL)
Member initializer list (MIL) enables data members of a class to be initialized before the constructor's function body, by calling their own constructors.
The order of the members in the list doesn’t matter; the actual execution order
is their order in the class declaration.
class Word
{
private:
char lang;
int freq;
char* str;
public:
Word() : lang('E'), freq(0), str(nullptr) { }; // used MIL
Word(const char* s, int f = 1, char g = 'E') : lang(g), freq(f) // used MIL
{ str = new char [strlen(s)+1]; strcpy(str, s); }
}
It is better to perform initialization by MIL than by assignments inside constructors.
Why? MIL calls the constructors of the data member. Howevever, assignment inside constructors is error-prone.
The code below is the example.
class Word_Pair
{
private:
Word w1; Word w2;
public:
// 1. MIL: w1 and w2 are initialized using conversion constructor of class Word (check the code above)
Word_Pair(const char* s1, const char* s2) : w1(s1, 5), w2(s2) { }
// 2. assignment: error prone because w1 and w2 are initialized using default constructor of class Word, then assignment.
// assignment operator function may not be properly defined (in case of pointer variables with dynamically allocated memory).
Word_Pair(const char* x, const char* y) { w1 = x; w2 = y; }
}
const
orreference
members MUST be initialized using MIL if they don’t have default initializers.- MIL always has priority over
default initialization
. This means that if a class has a const data member with default initializers, the const data can be modified using MIL. - Initialization of const/reference members cannot be done using
default arguments
.
Delegating constructor
The copy constructor can delegate
the conversion constructor using MIL
to create an object.
The copy constructor is now a delegating constructor.
Restriction: the delegated constructor must be the only item in the MIL.
class Word
{
private:
int frequency; char* str;
public:
Word(const char* s, int f = 1) // conversion constructor
{ frequency = f; str = new char [strlen(s)+1]; strcpy(str, s); } // this is same as the function set()
Word(const Word& w) : Word(w.str, w.frequency) // copy
}
Garbage Collection & Destructor
Memory usage on the runtime stack and heap
Local variables
- They are constructed when they are defined in a function/block on the
runtime stack
. - When the function/block terminates, the local variables inside and the call-by-value arguments will be
destructed
and removed on theruntime stack
. - Both construction and destruction of variables are done
automatically
by the compiler by calling the appropriate constructors and destructors.
Dynamically allocated memory
- It
remains
after function/block terminates, and it is the user’s reponsibilty to return it back to the heap for recycling usingdelete
; otherwise, it will stay until the program finishes. Garbage
: 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.
Destructor
The destructor of a class is invoked automatically
whenever its object goes out of scope.
- It takes
no arguments
, and hasno return type
. - There can only be one destructor for a class. This means no overloading is allowed.
- If no destructor is defined, the compiler will automatically generate a
default destructor
which does nothing. - The destructor itself does not actually release the object's memory. It does any assigned things before an object is ready to be destroyed.
User-defined destructor
User-defined destructors are usually needed when there are pointer members pointing to memory dynamically allocated by constructor of the class.
class Word
{
private:
int frequency; char* str;
public:
Word(const char* s, int k = 0) : frequency(k)
{ str = new char [strlen(s)+1]; strcpy(str, s); }
~Word() { delete [] str; } // destructor member function to deallocate dyn. allocated char array.
}
Member functions that are implicitly generated by the compiler
:
- default constructor
- default copy constructor
- default (copy) assignment operator function
- default move constructor
- default move assignment operator function
- default destructor
If you want to explicitly:
- generate the member functions above: use
= default;
- not generate the member functions above: use
= delete;
(useful when you don’t want any copy constructor)
class Word
{
private: ...
public:
Word() = default;
Word(const Word& w) = delete; // Words can't be copied. Error will occur if copy constructor is called.
}
Order of Construction and Destruction
“has” relationship:
- If an object A has an object B as a data member ->
A has a B
.
“owns” relationship:
- If an object A only has a
pointer
pointing to B ->A owns a B
. - If B has a memory on the heap, A is responsible for B’s destruction.
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.
class Clock
{
public:
Clock();
~Clock();
};
class Postoffice
{
Clock* clock;
public:
Postoffice() { clock = new Clock;}
~Postoffice(){ delete clock; }
}
int main()
{
cout << "Beginning of main\n";
Postoffice x;
cout << "End of main\n";
}
Order of construction and destruction:
- Beginning of main
- Clock constructor
- Postoffice constructor
- End of main
- Postoffice destructor
- Clock destructor
Leave a comment