Add a constructor, an initializer list, and a destructor
Constructor Initializer list in C++
Add a constructor, an initializer list, and a destructor to the person class you created earlier. We can also use a constructor to initialize sub-elements of an object.
Constructor initializers are specified in a comma-separated list that follows the constructor parameter list and is preceded by a colon.
An initializer's form is member name (expression list)
For example:
foo::foo(int* t):i(7), x(9.8), z(t) //initializer list
{ //other executable code follows here ....}
We will now recode the mod_int constructor so it initializes the class member v. Instead of coding the constructor like this:
mod_int::mod_int(int i = 0)
{ v = i % modulus; }
we can code it using an initializer list like this:
//Default constructor for mod_int
mod_int::mod_int(int i = 0) : v(i % modulus){}
Initialization versus Assignment in C++
Notice how initialization replaced assignment. In general, initialization is preferred to assignment. It is not always possible to assign values to members in the body of the constructor. An initializer list is required, however, when a nonstatic member is either a const or a reference. When members are themselves classes with constructors, the expression list is matched to the appropriate constructor signature to invoke the correct overloaded constructor.
In constructors, initialization is preferred to assignment. For example:
ch_stack::ch_stack(int size){
s = new char[size];
max_len = size;
top = EMPTY;
}
is better written as
ch_stack::ch_stack(int size):
max_len(size), top(EMPTY)
{ s = new char[size]; }
Data members that are reference declarations or const declarations must be initialized.
Also, the compiler can often be more efficient about initialization.
Before we continue, it is important to understand that C++ defines two distinct types of situations in which the value of one object is given to another.
The first is assignment and the second is initialization, which can occur in three ways:
When one object explicitly initializes another, such as in a declaration,
When a copy of an object is made to be passed to a function, or
When a temporary object is generated (most commonly, as a return value).
The copy constructor in C++ is not limited to just initializations.** While it's commonly used during initialization, it can also be invoked in other scenarios:
Initialization:* When a new object is declared and initialized with an existing object of the same type.
When an object is passed by value as an argument to a function.
When an object is returned by value from a function.
Assignment:* When an existing object is assigned the value of another object of the same type using the assignment operator (`=`). If a user-defined copy assignment operator is not provided, the compiler may generate one that internally uses the copy constructor.
Other situations:* When an object is inserted into a standard container (like `std::vector` or `std::list`) that stores objects by value.
In some cases, during exception handling when an exception object is copied.
Key Points:
Compiler-generated copy constructor: If you don't define a copy constructor for your class, the compiler will generate a default one that performs a member-wise copy of the object's data members.
Deep vs. shallow copy: The default copy constructor performs a shallow copy, which can lead to issues if your class contains pointers or dynamically allocated memory. In such cases, you need to define a custom copy constructor to perform a deep copy to ensure proper resource management.
Copy elision: In some cases, the compiler may optimize away unnecessary copy constructor calls through a technique called copy elision. This can happen during return value optimization or when initializing an object directly from another object.
Example:
class MyClass {
public:
int* data;
MyClass(int value) {
data = new int;
*data = value;
}
// User-defined copy constructor for deep copy
MyClass(const MyClass& other) {
data = new int;
*data = *(other.data);
}
~MyClass() {
delete data;
}
};
int main() {
MyClass obj1(10);
MyClass obj2 = obj1; // Copy constructor called during initialization
obj2 = obj1; // Copy constructor may be called during assignment (depends on compiler optimizations)
return 0;
}
In conclusion, while the copy constructor is frequently used during initialization, its role extends beyond that. It's essential for handling object copying in various scenarios throughout your C++ code. Understanding when and how the copy constructor is invoked is crucial for writing correct and efficient C++ programs, especially when dealing with classes that manage resources.
Copy Constructor
The most common general form of a copy constructor is shown here:
classname (const classname &obj) {
// Body of copy constructor.
}
Here, obj is a reference to the object on the right side of the initialization. It is permissible for a copy constructor to have additional parameters as long as they have default arguments defined for them. However, in all cases, the first parameter must be a reference to the object doing the initializing.
This reference can also be const and/or volatile. Again, assume a class called myclass and an object of type myclass called A. Also assuming that func1( ) takes a myclass parameter and that func2( ) returns a myclass object, each of the following statements involves initialization:
myclass B = A; // A initializing B
myclass B(A); // A initializing B
func1(A); // A passed as a parameter
A = func2(); // A receiving a temporary, return object
In the first three cases, a reference to A is passed to the copy constructor.
In the fourth, a reference to the object returned by func2( ) is passed to the copy constructor.Inside a copy constructor, you must manually handle the duplication of every field within the object. This, of course, gives you a chance to avoid potentially harmful situations. For example, in myclass just described, the new myclass object could allocate its own memory. This would allow both the original and the copy to be equivalent but fully separate objects. It also avoids the problem of both objects using the same memory because freeing one object's memory would not affect the other.
If necessary, the memory could be initialized to contain the same contents as the original.
In some cases, the same problems that can occur when making a copy of an object also occur when one object is assigned to another. The reason is that the default assignment operator makes a member-by-member, identical copy. You can avoid problems by overloading operator=( ) so that you handle the assignment process yourself.
Purpose: Supply the implementation of a constructor, initializing data fields before the body of the constructor.
Constructor Initializers - Exercise
Click the exercise link below to add a constructor, an initializer list, and a destructor to the person class you created earlier. Constructor Initializers - Exercise