Presentation is loading. Please wait.

Presentation is loading. Please wait.

Object programming essentials

Similar presentations


Presentation on theme: "Object programming essentials"— Presentation transcript:

1 Object programming essentials
Chapter 5 (Part 2) Object programming essentials

2 Chapter 5 Objectives Create objects based on objects of other objects of custom classes  Understand the concept of inheritance syntax and operation Share functionality between objects using inheritance Implement data structures in C++ Understand the concept of dynamic allocation of C++ objects Prevent memory leaks and deallocate acquired resources Provide derived data about the implemented data structure Keep the data structure consistent at all times Traverse data structures Access data stored in data structures Create copies of data structures Implement and use copy constructors

3 5.4 Static components

4 5.4.1 The “auto” keyword (1) The “auto” keyword came from the ancestor of “C++”, the “C” programming language. All the variables in your code belong to one of two categories. They are: automatic variables, created and destroyed automatically during program execution static variables, existing continuously during the whole program execution  The “C” and “C++” programming languages assume that all variables are automatic by default unless they are declared explicitly as static.

5 5.4.1 The “auto” keyword (1) It’s interesting that the “auto” keyword is still commonly used in older versions of the “C” language to explicitly mark automatic variables. #include <iostream> using namespace std; void fun(void) { auto int var = 99; cout << "var = " << ++var << endl; } int main(void) { for(int i = 0; i < 5; i++) fun(); return 0; Output: var = 100

6 5.4.1 The “auto” keyword (1) The var variable is declared inside the fun function and is automatic. We’ve used the “auto” keyword, but the program behavior will remain the same if you remove the keyword (try it yourself). The variable is created each time the fun function is invoked and the variable is destroyed each time the function completes its execution. This means that, regardless of the number of invocations, the function will always produce the same output.

7 5.4.2 The “auto” keyword (2) Now, replace the “auto” keyword with “static”. The code looks nearly identical, but its behavior has been changed radically. #include <iostream> using namespace std; void fun(void) { static int var = 99; cout << "var = " << ++var << endl; } int main(void) { for(int i = 0; i < 5; i++) fun(); return 0; Output: var = 100 var = 101 var = 102 var = 103 var = 104

8 5.4.3 Instances of the class The class is like a dummy be brought to life when its incarnation (in this case its object) is created. Every object created from a particular class is named a class’s instance. From this point of view, each instance of the class is a separate universe and has nothing to do with any of the remaining instances. All the object’s components (fields and functions) are enclosed inside the instance.

9 5.4.3 Instances of the class In consequence, we mustn’t use any of the class components until we’ve created an object of that class. The following snippet is wrong and will cause a compilation error. #include <iostream> using namespace std; class Class { public: int val; void print(void) { cout << val << endl; } }; int main(void) { Class::val = 0; //Error Class::print(); //Error return 0; }

10 5.4.4 Static components of the class
All the rules on the previous slide are true if they refer to the non-static components of the class (both fields and functions). The “C++” language allows us to define other kinds of components too, they’re called “static components”. A static component exists throughout the whole life of the program. Moreover, there is always only one component regardless of the number of instances of the class. We can say that all the instances share the same static components.

11 5.4.4 Static components of the class
Output: Static = 1, NonStatic = 10 Static = 2, NonStatic = 20

12 5.4.4 Static components of the class
The static variable has to have both an explicitly expressed separate definition a possible initialization, and both must be placed outside the class definition. Another rationale says that the definition has to be separate from the class body because static variables are not actually part of any object. In our program, we do this with a line of the code between the Class body and the main function. Removing this line generates an error.

13 5.4.5 Static class variables (1)
The static class variables usually used as counters of instances of a particular class. The next program implements this idea in a very simple way. We’ll increment the static variable Counter inside the Class constructor and decrement it inside the Class destructor Note once again that the Counter field is accessed directly when it’s being used inside the class and with the “::” operator when it’s being used outside the class.

14 5.4.5 Static class variables (1)
using namespace std; class Class { public: static int Counter; Class(void) { ++Counter; }; ~Class(void) { --Counter; if(Counter == 0) cout << "Bye, bye!" << endl; }; void HowMany(void) { cout << Counter << " instances" << endl; } int Class::Counter = 0; int main(void) { Class a; Class b; cout << Class::Counter << " instances so far" << endl; Class c; Class d; d.HowMany(); return 0; } Output: 2 instances so far 4 instances Bye, bye!

15 5.4.6 Static class variables (2)
Static variable can be defined private in the class This will obviously prevent direct access to the variable, but it may be something we want if we want to protect the value against any unauthorized modification. Note that any attempts to access the Counter variable expressed like this: Class::Counter = 1; are strictly prohibited.

16 5.4.6 Static class variables (2)
Output: 2 instances 4 instances Bye, bye!

17 5.4.7 Static class functions
It’s not only class variables that can be declared as static – functions can be declared like this, too. The static function, like a static variable, may also be accessed (or more precisely, invoked) when no instances of the class have been created. Note that the static function may be invoked from inside the class, like this: Class::HowMany(); or by using any of the existing instances, like this: b.HowMany();

18 5.4.7 Static class functions
Output: 0 instances 2 instances 4 instances Bye, bye!

19 5.4.8 Static vs. non-static components
The coexistence of both static and non-static components within a single class causes some additional issues which we need to take into consideration.

20 5.4.9 Static → static interaction
The first test program demonstrates a case when a static function tries to invoke another static function. A case like this is always possible, as both functions are available during the entire life of the program. Output: static

21 5.4.11 Static → non-static interaction
The second test program demonstrates a case when a static function  tries to invoke a non-static function. A case like this is not possible, as the function being invoked exists when and only when any of the objects which contain this function also exists. The function cannot be successfully accessed without specifying the associated object. Syntax Error Static member can’t invoke non-static member

22 5.4.13 Non-static → static interaction
The third test case refers to the situation where a non-static function invokes a static function. A case like this is always possible, as the static function is available before any object has been created. Output: static

23 5.4.15 Static vs. non-static components
The last remaining option is simple. We don’t need to do any experiment to find out the answer to the following question: Is it possible to invoke a non-static function from within a non-static function? Yes, obviously it is possible. What’s more, we’ve done it many times before.

24 Example

25 5.5 Objects vs. pointers and objects inside the objects

26 5.5.1 Pointers to objects So far we’ve treated objects like ordinary variables and assumed that an object is created in the place where it is declared and destroyed when its declaration scope is exited. Objects may also exist as dynamically created and destroyed entities. In other words, objects may appear on demand – when they’re needed – and vanish in the same way.

27 5.5.1 Pointers to objects Object constructed! Object destructed!
Output: Object constructed! Object destructed!

28 5.5.1 Pointers to objects We’ve created one object of that class using the new keyword. Note that we can omit the empty parentheses after the Class name – in either case, the parameter-less constructor will be activated. The object is destroyed using the delete keyword. The process of destroying the object begins with the implicit invocation of its destructor.

29 5.5.2 Pointers to fields All the variables, including objects, brought to life in the “ordinary” way live in a separate area of memory called the stack. It’s a memory region dedicated to storing all automatic entities. The stack grows when new automatic variables are created and shrinks when the variables are no longer needed. Note that this process is beyond your control. You cannot affect the way in which the stack changes.

30 5.5.2 Pointers to fields The entities created “on demand” (by the new keyword) are created in a specific memory region usually called a heap. In contrary to the stack, the heap is fully under your control. You decide how many variables, arrays, objects, etc. will occupy the heap and it’s up to you when these entities end their lives. The object being stored in the heap must be accessed in a way that resembles the access to the dynamically allocated structures. You mustn’t use the ordinary “dotted” notation as there’s no structure (object) which can play the role of the left argument of the “.” operator unless you dereference the pointer.  You need to use the “arrow” (->) operator instead.

31 5.5.2 Pointers to fields The general rule says that:
if S is a structure or class and S has a component named C and if p is a pointer to a structure of type S, then the C component may be accessed in the two following ways: (*p).C;  // p is explicitly dereferenced in order to access the C component p->C;    // p is implicitly dereferenced in order to access the C component

32 5.5.2 Pointers to fields We’ve added one field to the Class.
It’s declared in the public part of the class so you can access it freely from outside the class. There’s one catch – you have to use the “->” operator. You can use * with . Instead of -> operator. ptr->value = 0; (*ptr).value = 0; Look at the parentheses around the argument of the ++ operator. Do we really need them? What would happen when you removed them?

33 5.5.2 Pointers to fields Object constructed! 1 Object destructed!
Output: Object constructed! 1 Object destructed!

34 5.5.3. Pointers to functions Object constructed! Value = 2
Member functions invoked for an object accessed through the pointer have to be accessed using the arrow operator, too. Output: Object constructed! Value = 2 Object destructed!

35 5.5.4 Selecting the constructor
If a class has more than one constructor, one of them may be chosen during object creation. This is done by specifying the form of the parameter list associated with the class name. The list should be unambiguously compatible with one of the available class constructors. We’ve modified our program again. There are already two constructors. We’ve created two new objects inside the main functions. They differ in the constructor used to build each of the objects. In effect, their value fields have different values assigned to them.

36 5.5.4 Selecting the constructor
class Class { public: Class(void) { cout << "Object constructed (#1)" << endl; } Class(int v) { value = v; cout << "Object constructed (#2)" << endl; } ~Class(void) { cout << "Object destructed! val = " << value << endl; } void IncAndPrint(void) { cout << "value = " << ++value << endl; } int value; }; int main(void) { Class *ptr1, *ptr2; ptr1 = new Class; ptr2 = new Class(2); ptr1 -> value = 1; ptr1 -> IncAndPrint(); ptr2 -> IncAndPrint(); delete ptr2; delete ptr1; return 0; Output: Object constructed (#1) Object constructed (#2) value = 2 value = 3 Object destructed! val = 3 Object destructed! val = 2

37 Classes and Pointers: Some Peculiarities
C++ Programming: From Problem Analysis to Program Design, Fourth Edition Classes and Pointers: Some Peculiarities There are three things to take care when the class uses pointer members: Destructor to delete the dynamic memory Overload the assignment operator Override the Copy constructor

38 Memory leaks Many of the objects are allocated memory that they need for their operation. This memory should be released when the object finishes its activity and the best way to do this is to do the cleaning automatically. Failure to clean the memory will cause a “memory leaking”, where the unused (but still allocated!) memory grows in size, affecting system performance. Take a look at the example on the following slides. The Class class has only one constructor, which is responsible for allocating memory of the size specified by its parameter value. The object of this class is created as a local variable inside the MakeALeak() functions.

39 Memory leaks #include <iostream> using namespace std; class Class { public: Class(int val) { value = new int[val]; cout << "Allocation (" << val << ") done." << endl; } int *value; }; void MakeALeak(void) { Class object(1000); int main(void) { MakeALeak(); return 0;

40 Memory leaks the constructor explicitly allocates another part of the memory The object variable is an example of an “automatic variable”. This means that the variable automatically finishes its life when the execution of the function containing the variable’s declaration ends. We can expect that a return from the MakeALeak() functions will cause the following action: the memory allocated to the object itself is freed(this is done implicitly). Unfortunately, the memory explicitly allocated by the constructor remains allocated. To make matters worse, we’ve lost the only pointer that held the address of that memory (it was stored by the value field, but the object containing this field doesn’t exist anymore). The fairly large portion of memory has leaked.

41 C++ Programming: From Problem Analysis to Program Design, Fourth Edition
Destructor If objectOne goes out of scope, the member variables of objectOne are destroyed The memory space of the dynamic array would stay marked as allocated, even though it cannot be accessed Solution: Put the necessary code in the destructor to ensure that when objectOne goes out of scope, the memory of the array is deallocated

42 Destructors #include <iostream> using namespace std; class Class { public: Class(int val) { value = new int[val]; cout << "Allocation (" << val << ") done." << endl; } ~Class(void) { delete [ ] value; cout << "Deletion done." << endl; int *value; }; void MakeALeak(void) { Class object(1000); int main(void) { MakeALeak(); return 0;

43 Copying constructors There is a special kind of constructor intended to copy one object into another. They are referred to as copying constructors and are implicitly invoked when a declaration of an object is followed by an initiator. Constructors of this kind have one parameter referenced to an object of the same class and are used to copy all important data from the source object to the newly created object (or more precisely, to the object currently being created).

44 Copying constructors Output: 124 123

45 Copying constructors If the copying constructor doesn’t exist within a particular class and the initiator is actually used during the declaration of an object, its content will be copied “field by field”, as if the object had been cloned. The copying constructor will also be used when the context requires a copy of a specific object, e.g. when a particular object is transferred to a function as a value-passed actual parameter.

46 5.3.11 Copying constructors Output: Hi from the copy constructor!
I'm here!

47 Copying constructors The following example shows two different classes. The former does have a copying constructor, while the latter doesn’t. Two objects are created for both of the classes while both of the second objects are created by copying the first ones.

48 5.3.11 Copying constructors Output: 200
class Class1 { public: Class1(int val) { this -> value = val; } Class1(Class1 const &source) { value = source.value + 100; } int value; }; class Class2 { Class2(int val) { this -> value = val; } int main(void) { Class1 object11(100), object12 = object11; Class2 object21(200), object22 = object21; cout << object12.value << endl; cout << object22.value << endl; return 0; } Output: 200

49 Copy Constructor (continued)
C++ Programming: From Problem Analysis to Program Design, Fourth Edition Copy Constructor (continued) Copy constructor automatically executes in three situations: When an object is declared and initialized by using the value of another object When, as a parameter, an object is passed by value When the return value of a function is an object

50 Copy Constructor with pointer member
C++ Programming: From Problem Analysis to Program Design, Fourth Edition Copy Constructor with pointer member This initialization is called the default member-wise initialization Initialization due to the constructor, called the copy constructor (provided by the compiler)

51 Copy Constructor (continued)
C++ Programming: From Problem Analysis to Program Design, Fourth Edition Copy Constructor (continued) Default initialization leads to shallow copying of data Similar problem occurs when passing objects by value: Solution: write a user copy constructor to do a deep copy

52 Default copy constructor
class List{ int *data; int size; public: List(int s) { size = s; data = new int[size]; } void fill() { for(int i = 0; i < size; i++) data[i] = i*i; } void print() cout<<data[i]<<" "; cout<<endl; ~List() {delete []data;} }; void fun(List obj2) {} void main() { List obj1(5); obj1.fill(); fun(obj1); obj1.print();} Default copy constructor obj2 obj1 Output: Rubbish values

53 User defined copy constructor
class List{ int *data; int size; public: List(int s) { size = s; data = new int[size]; } List(List &other) { size = other.size; data = new int[size]; for(int i = 0; i < size; i++) data[i] = other.data[i]; } void fill() { data[i] = i*i; void print() cout<<data[i]<<" "; cout<<endl; ~List() {delete []data;} }; void fun(List obj2) {} void main() List obj1(5); obj1.fill(); fun(obj1); obj1.print();} User defined copy constructor obj1 obj2 Output:

54 5.5.5 Arrays of pointers to objects (1)
There’s a class that implements a very simple array containing elements of type int. The size of the array is determined by the parameter’s value passed to the constructor of the class. The class offers us two methods for accessing the array. The first (named Get) returns the value of the element stored in the cell of an index specified by the value of the parameter. The second (named Put) is able to set a new value of the selected cell (the parameters of the functions specify the index and the value respectively). Yes, we agree, both these methods are extremely reckless: they don’t check the index value in any way.

55 5.5.5 Arrays of pointers to objects (1)
class ArrayList { int *values; int size; public: ArrayList(int siz) { size = siz; values = new int[size]; cout << "Array of " << size << " ints constructed." << endl; } ~ArrayList(void) { delete [] values; cout << "Array of " << size << " ints destructed." << endl; int Get(int ix) { return values[ix]; } void Put(int ix, int val) { values[ix] = val; } }; int main(void) { ArrayList *arr = new Array(2); for(int i = 0; i < 2; i++) arr->Put(i, i + 100); cout << "#" << i + 1 << ":" << arr->Get(i) << endl; delete arr; } Output: Array of 2 ints constructed. #1:100 #2:101 Array of 2 ints destructed.

56 5.5.6 Arrays of pointers to objects (2)
There are no obstacles to gathering pointers to objects inside an array. We’ve used two objects of the Array class and stored pointers to them in the arr array (don’t confuse these two entities: Array is a class defined by us, arr is an ordinary, language built-in array). Now carefully follow the flow of the program and note the syntax we’ve used to access each of the existing objects.

57 5.5.6 Arrays of pointers to objects (2)
class ArrayList { int *values; int size; public: ArrayList(int siz) { size = siz; values = new int[size]; cout << "Array of " << size << " ints constructed." << endl; } ~ArrayList(void) { delete [] values; cout << "Array of " << size << " ints destructed." << endl; int Get(int ix) { return values[ix]; } void Put(int ix, int val) { values[ix] = val; } }; int main(void) { ArrayList *arr[2] = { new ArrayList(2), new ArrayList(2) }; for(int i = 0; i < 2; i++) for(int j = 0; j < 2; j++) arr[i]->Put(j, j i); for(int i = 0; i < 2; i++) { cout << "#" << i + 1 << ":" << arr[i]->Get(j) ; cout << endl; delete arr[0]; delete arr[1]; } Output: Array of 2 ints constructed. #1:100 #2:101 Array of 2 ints destructed.

58 Objects inside objects (Composition)

59 5.5.7 Objects inside objects (1)
An object of any class may be the field of an object of any other class. All rules concerning access to class components are honored in this case too. There’s a very simple class named Element. It’s intended to store one int value. The second class displayed in the example is also simple, but there’s an interesting thing about it: there are two fields of the Element class. As you can see, we can manipulate them with no great difficulty. They behave just like any other class component. There are three objects in our example program:  coll(visible at the main function level) and el1 together with el2 (visible at the coll object level). We want to know the sequence in which all constructors work.

60 5.5.7 Objects inside objects (1)
class Element { int value; public: int Get(void) { return value; } void Put(int val) { value = val; } }; class Collection { Element el1, el2; int Get(int elno) { return elno == 1 ? el1.Get() : el2.Get(); } int Put(int elno, int val) { if(elno == 1) el1.Put(val); else el2.Put(val); } int main(void) { Collection coll; for(int i = 1; i <= 2; i++) coll.Put(i, i + 1); cout << "Element #" << i << " = " << coll.Get(i) << endl; return 0; } Element #1 = 2 Element #2 = 3

61 5.5.8 Objects inside objects (2)
Here goes the modified code We’ve cleaned the main function of everything that isn’t really needed to track the constructors activity. The conclusion is: constructors from inner objects (objects stored inside other objects) are invoked before the outer object’s constructors start their work. This rule should be applied repeatedly.

62 5.5.8 Objects inside objects (2)
class Element { int value; public: Element(void) { cout << "Element constructed!" << endl; } int Get(void) { return value; } void Put(int val) { value = val; } }; class Collection { Element el1, el2; Collection(void) { cout << "Collection constructed!" << endl; } int Get(int elno) { return elno == 1 ? el1.Get() : el2.Get(); } int Put(int elno, int val) { if(elno == 1) el1.Put(val); else el2.Put(val); } int main(void) { Collection coll; return 0; } Element constructed! Collection constructed!

63 5.5.9 Objects inside objects (3)
We’ve changed the form of the Element class’ constructor – before it was a parameter-less function, now it needs one parameter of type int. The modification has invalidated the program – it can’t be compiled. The compiler will produce an error message saying something like this: In constructor 'Collection::Collection()': error: no matching function for call to 'Element::Element()' The constructor invoked implicitly (sometimes called the default constructor) is the one which has no parameters. The only available constructor is compatible neither with the default one, nor with the copying constructor, and this makes our program incorrect. How can we deal with this? Is there a trick to tell the compiler that we want it to use the other constructor instead of the default one? Yes, there is. The “C++” language offers us a special syntax for this and similar situations.

64 5.5.9 Objects inside objects (3)
class Element { int value; public: Element(int val) { value = val; cout << "Element(" << val << ") constructed!" << endl; } int Get(void) { return value; } void Put(int val) { value = val; } }; class Collection { Element el1, el2; Collection(void) { cout << "Collection constructed!" << endl; } int Get(int elno) { return elno == 1 ? el1.Get() : el2.Get(); } int Put(int elno, int val) { if(elno == 1) el1.Put(val); else el2.Put(val); } int main(void) { Collection coll; } Syntax Error 'Element' : no appropriate default constructor available

65 5.5.10 Objects inside objects (4)
If we want a constructor other than the default one to be invoked during the creation of an object which is part of another object, we should use the following syntax Class(…) : inner_field_constr1(…), inner_field_constr2(…) { … } This means that you have to list all the inner objects’ constructors that you wish to use instead of the default constructors. This is expressed in the line saying: Collection(void) : el2(2), el1(1) { … }

66 5.5.10 Objects inside objects (4)
class Element { int value; public: Element(int val) { value = val; cout << "Element(" << val << ") constructed!" << endl; } int Get(void) { return value; } void Put(int val) { value = val; } }; class Collection { Element el1, el2; Collection(void) : el2(2), el1(1) { cout << "Collection constructed!" << endl; } int Get(int elno) { return elno == 1 ? el1.Get() : el2.Get(); } int Put(int elno, int val) { if(elno == 1) el1.Put(val); else el2.Put(val); } int main(void) { Collection coll; return 0; Element(1) constructed! Element(2) constructed! Collection constructed!

67 5.5.10 Objects inside objects (4)
Note that the inner constructors have been invoked in the sequence reflecting the order of the declaration inside the Collections class (el1 first), not in the order in which the constructors were listed in the Class constructor header (el2 first).

68 5.5.10 Objects inside objects (4)
And by the way, there’s the following alternation for this case: when the constructor is divided between the declaration and the definition, the list of alternative constructors should be associated with the definition, not the declaration. This means that the following snippet is correct: class X { public: X(int x) { }; }; class Y { X x; Y(int x); Y::Y(int x) : x(1) { };

69 objB 5 z objA 3 x 4 y

70 objC objB objA objA


Download ppt "Object programming essentials"

Similar presentations


Ads by Google