Object Creation and Destruction
Object Construction
- Constructors can be called directly during the object creation.
- It has no return type
e.g.
class Point {
private:
int x;
int y;
public:
Point() {
x = 0;
y = 0;
}
Point(int init_x, int init_y) {
x = init_x;
y = init_y;
}
}
int main(void) {
Point p1;
Point p2(4,5);
return EXIT_SUCCESS;
}
- As the constructor is inside of an objet, it has an explicit this parameter, which points at the newly created object
Overloading
- If we don't write any constructor, C++ compiler will provide a default constructor that basically behaves as if we declared like this:
class MyClass {
public:
MyClass() {}
};
- If we write any other constructor, C++ compiler does not provide this constructor for you.
- If we have written any constructors, the class is non-POD by virtue of having a constructor.
- A class type that has a public default constructor - whether explicitly declared or automatically provided - is called a default constructible class.
Dynamic Allocation
- To allocate memory in C++, we can use the new operator:
- It allocates memory for one object of type Bankaccount and calls constructor to initialize the newly created object.
i.e.
BankAccount * accountPtr = new BankAccount();
- This expression: new, evaluates to a pointer to the newly allocated object and has type BankAccount *.
- We can use new[] to allocate space for an array.
i.e.
BankAccount * accountArray = new BankAccount[42]();
- But if we create an array of objects, we can only have them constructed with their default constructor.
- We can also create an array of 3 pointers to point:
i.e.
Point ** point_ptrs = new Point * [3];
Types of Initialization
- There are two statements:
BankAccount * accountPtr = new BankAccount();
BankAccount * accountPtr = new BankAccount;
- The first one uses value initialization. The second uses default initialization. Each of these has different behavior and the specifics depend on whether the type being initialized is POD or non-POD.
- When value initialization is used,
1) a class with a default constructor is initialized by its default constructor
2) a class without any constructor has every field value initialized
3) Non-class types are zero initialized
4) Array have their elements value initialized
- When default initialization is used,
1) non-POD types are initialized by their default constructor
2) POD types are left initialized
3) Arras have their elements default initialized
- The best approach is to just always include a default constructor in the class.
Initializer Lists
- In above section, while constructor for the BankAccount works fine, it makes use of traditional assignment statements, rather than the preferred C++ approach.
- C++ uses an initializer list.
- An initializer list is a list of initializations written by placing a colon after the close parenthesis of the constructor's parameter list and writing a sequence of initializations before the open curly brace of the function's body.
e.g.
BankAccount::BankAccount() : balance(0) {}
BankAccount::BankAccount(double initBal) : balance(initBal){}
-Each initialization takes the form name(values)
e.g. for constructor:
BankAccount::BankAccount() : accountNum(nextActNum,
balance(0.0)) {
nextActNum++;
}
BankAccount::BankAccount(double b) : accountNum(nextActNum,
balance(b)) {
nextActNum++;
}
-The reason why we use initialization instead of assignment is that C++ makes a distinction between initialization and assignment.
- One important reason is that C++ ensures all fields have some form of initialization before the open curly brace of the constructor. If we specify it, we get exactly what we want, or it will be default initialized.
- default initialization for POD types leaves them with unspecified value. So, we'd better use this way to initialize.
- If we use assignment statement, it actually does not specify the initialization behavior. Instead, if we use assignment, it will assign to the already created object, changing its fields. It's slower and may have some noticeable effects.
-If the class has fields of a reference type, we must initialize them in the initializer list. Or we will receive error message.
- If the class contains a variable which has const modifier, we must initialize it using initialized list.
- The order in which fields are declared is the order in which they are declared in the class. Not their initializers order. In some cases it is important.
- Arguments that are not explicitly initialized will be initialized by the default constructor.
What We Do
We should
- Make classes default constructible by writing default constructor in every class that we write.
- Using initializer list to initialize class's fields instead of initializing them in the constructor's body.
- In the initializer list, explicitly initialize every field
- Initialize the fields in the order they are declared.
- Use new and new [] instead of malloc.
Object Destruction
- A destructor is named the same as the class it appears in, except with a tilde (~)
- Destructor doesn't have return type, not even void.
- Destructor cannot be overloaded. A class may only have one destructor and it must take no parameters.
- Destructor is inside the class, therefore it receives an implicit this parameter.
- It can be public or private.
- Use delete and delete() to free the memory allocated by new and new[].
When are destructors invoked
- A destructor is invoked whenever the "box" for an object is about to be destroyed.
- This destruction can happen either due to dynamic deallocation using delete or delete[], by a local variable going out of scope, or by one object that contains another eing destroyed.
- "Box" is only actually destroyed after the destructor completes.
- The order of destruction is the opposite of the order of construction.
- For local variables, their "box" is destroyed whenever they go out of scope. Before they are destroyed, their destructors are invoked in the opposite order they are constructed.
- Fields inside of a class are also destroyed as part of the destruction of the enclosing object. After the class's destructor completes, each of the fields is destroyed in the reverse of the order in which they initialized.
- plain old data: no object
Object Copying
- just passing the object file in the function will result in segmentation fault.
- We should not use naive copy to copy object
- C++ distinguishes between two types of copying: copying during initialization (the copy constructor) and copying during assignment (the copy assignment operator)
Copy Constructor
- Copying during initialization occurs when a new object is created as a copy of an old one.
e.g.
1. when objects are passed to functions by value
2. when an object is returned from a function by value
3. explicitly when the programmer writes another object of the same type as the initializer for a newly declared object
- Whatever the reason for initializing an object from a copy of another object, the copy constructor is invoked to perform the copying in the fashion the class defines.
- The copy constructor has no return type.
- It is very specific overloading of the constructor.
e.g.
Polygon(const Polygon & rhs) : points(new Points[rhs.numPoints]),
numPoints(rhs.numPoints){
for (size_t i = 0; i < numPoints; i++) {
points[i] = rhs.points[i];
}
}
- If we don't explicitly specify a copy constructor, C++ will automatically provide one.
- The provided one performs as if it contains an initializer list which initializes every field from the corresponding field in the argument. i.e.
class SomeClass {
Type1 field1;
Type2 field2;
...
TypeN fieldN;
}
public :
SomeClass (const someClass & rhs):field1(rhs.field1),
field2(rhs.field2),
...
fieldN(rhs.fieldN) {}
-for non-class types, the initialization simply copies the value. For fields of class types, this initialization invokes the corresponding copy constructor.
- With default constructors, a copy constructor may be classified as trivial. (automatically provided by the compiler)
Assignment Operator
- previous: copy during initialization
-copy during assignment changes the value of an object that already exists
-copy by assignment makes use of assignment operator, opeartor=.
- difference between assignment operator and the copy constructor
-difference between assignment operator and copy operator
1. assignment operator returns a value
2. operator begins with this != &rhs
3. assign constructor must clean up the existing object before we assign the value
- default assignment operator: not contain self-assignment before coping.
Rule of Three
- If you write a destructor, copy constructor. or assignment operator, then you must write all three of them.
Unnamed Temporaries
- We must be careful to treat unnamed temporaries when they are objects.
- a + b * c: b * c is an unnamed temporary with an object type. The creation and destruction of this object is nontrivial.
- The simplest way of creating unnamed temporaries is to declare an object with no name by writing the class name followed by parentheses and the initializers of the object.
- Such a statement will allocate space for an object of type MyClass in the current stack frame but without name. Then, it will pass 42 to its constructor. After constructor, this object will be destroyed immediately. The destructor will be invoked to clean up the object.
- An unnamed temporary object is deallocated at the end of the full expression.
-x = MyClass(42) + MyClass(17) + MyClass(3);
It create five unnamed temporaries, these three and the result of the multiplication, and the result of addition. Order are not guaranteed.
Parameter Passing
- Unnamed temporaries can be useful in passing parameters to functions. e.g.
someFunction(MyClass(42));
- Most implementations will have the called function destroy everything except the parameters, and the caller destroy the parameters.
- It is generally preferable to have functions take a const reference rather than a copy of an object.e.g.
int someFunction(const MyClass & something) {...};
In above statement, no copying is involved in any case. The compiler will create a "box" for the unnamed temporary and pass the address of that box as the pointer that is the reference.
-If the reference is non-const, then we would not be able to pass an unnamed temporary, as it is not an lvalue.
Return Values
- The creation of unnamed temporary can happen in two ways.
- Function creates an object, and that object is explicitly copied to initialize the unnamed temporary. It will use copy constructor.
- C++ allows the compiler to elide the copy, even if the copy constructor has noticeable effects. And in this way the result is directly initialized from within the function. This optimization is called the return value optimization.
- When executing code by hand, we can choose whether to assume the compiler performs return value optimization or not. For most code, the optimization does not change the correctness or behavior of the code.
Implicit Conversions
- We can pass an unnamed temporary to someFunction, .e.g.
someFunction(MyClass(42));
-But we even can pass like this:
someFunction(42);
- In C++, any constructor that takes one argument is considered as a way to implicitly convert from its argument type to the type of the class it resides in, unless the constructor is declared explicit.
- The compiler is only allowed to apply this rule once per initialization. If class A has a constructor that takes a B, and B has a constructor that takes a C, then we may not pass a C where an A is required and expect the compiler to make a temporary B from which it then can then make a temporary A.
- We should declare all of our single-argument constructors except our copy constructors as explicit, such as:
explicit MyClass(int x) : someField(x) {...}
- This can help the compiler to detect our mistakes instead of translating it into a legal type
- IMPORTANT: All single-argumennt constructors should be declared as explicit, except for the copy constructor. And copy constructor should never be declared as explicit. It cannot be used for an implicit conversion.
summary& understanding after reading <<All of Programming>> Chapter 15
Comments
Post a Comment