860 likes | 1.01k Views
Dynamic object creation. Object creation. When a C++ object is created, two events occur: Storage is allocated for the object. The constructor is called to initialize that storage. Step one can occur in several ways
E N D
Object creation • When a C++ object is created, two events occur: • Storage is allocated for the object. • The constructor is called to initialize that storage. • Step one can occur in several ways • Storage can be allocated before the program begins, in the static storage area. This storage exists for the life of the program. • Storage can be created on the stack whenever a particular execution point is reached (an opening brace). That storage is released automatically at the complementary execution point (the closing brace). • Storage can be allocated from a pool of memory called the heap (also known as the free store)
Operator new • C++ can combine all the actions necessary to create an object into a single operator called new. • When you create an object with new (using a new-expression), it allocates enough storage on the heap to hold the object and calls the constructor for that storage. MyType *fp = new MyType(1,2); • Here at runtime the equivalent of malloc(sizeof(MyType) i)s called (often, it is literally a call to malloc( )), and the constructor for MyType is called with the resulting address as the this pointer, using (1,2) as the argument list • The default new checks to make sure the memory allocation was successful before passing the address to the constructor. • If the constructor has no arguments, you can write the new-expression without the constructor argument list: MyType *fp = new MyType;
Operator delete • The complement to the new-expression is the delete-expression • it first calls the destructor and then releases the memory (often with a call to free( )). • Just as a new-expression returns a pointer to the object, a delete-expression requires the address of an object. delete fp; • delete can be called only for an object created by new
Memory manager overhead • When you create automatic objects on the stack, the compiler knows the exact type, quantity, and scope. • Creating objects on the heap involves additional overhead, both in time and in space. • You call malloc( ), which requests a block of memory from the pool • The block must be recorded so further calls to malloc( ) won’t use it, and so that when you call free( ), the system knows how much memory to release.
new & delete for arrays • In C++, we can create arrays of objects on the stack or on the heap with equal ease, and the constructor is called for each object in the array. • Ex: MyType* fp = new MyType[100]; • This allocates enough storage on the heap for 100 MyType objects and calls the constructor for each one. • For deleting the array we use the syntax delete []fp; • The empty brackets tell the compiler to generate code that fetches the number of objects in the array and calls the destructor for that many array objects.
Running out of storage • when the operator new cannot find a contiguous block of storage large enough to hold the desired object, a special function called the new-handler is called. • The default behavior for the new-handler is to throw an exception • if you’re using heap allocation in your program, it’s wise to replace the new-handler with a message that says you’ve run out of memory and then aborts the program. • The behavior of the new-handler is tied to operator new • so if you overload operator new the new-handler will not be called by default. • If you want to call the new-handler, you have to write the code to do so inside your overloaded operator new
Example #include <iostream> #include <cstdlib> #include <new> using namespace std; int count = 0; void out_of_memory() { cerr << "memory exhausted after " << count << " allocations!" << endl; exit(1); } int main() { set_new_handler(out_of_memory); while(1) { count++; new int[1000]; // Exhausts memory } }
Overloading new & delete • When we create a new-expression first, storage is allocated using the operator new and then the constructor is called. • In a delete-expression, the destructor is called and then storage is de allocated using the operator delete. • The constructor and destructor calls are never under our control, but we can change the storage allocation functions operator new and operator delete. • C++ allows you to overload new and delete to implement your own storage allocation scheme
Overloading new & delete • The most common reason to change the allocator is efficiency • Another issue is heap fragmentation. • By allocating objects of different sizes it’s possible to break up the heap so that you effectively run out of storage. • When you overload operator new and operator delete, it’s important to remember that you’re changing only the way raw storage is allocated. • The compiler will simply call your new instead of the default version to allocate storage, then call the constructor for that storage.
When you overload operator new, you also replace the behavior when it runs out of memory • so you must decide what to do in your operator new: return zero, write a loop to call the newhandler and retry allocation, or (typically) throw a bad_alloc exception
Overloading global new & delete • when the global versions of new and delete are unsatisfactory for the whole system, you can overload the global versions • That is make the defaults completely inaccessible –you can’t even call them from inside your redefinitions. • The overloaded new must take an argument of size_t (the Standard C standard type for sizes). • This argument is generated and passed to you by the compiler and is the size of the object you’re allocating. • You must return a pointer either to an object of that size or to zero if you can’t find the memory (in which case the constructor is not called!).
#include <cstdio> #include <cstdlib> using namespace std; void* operator new(size_t sz) { printf("operator new: %d Bytes\n", sz); void* m = malloc(sz); if(!m) puts("out of memory"); return m; } void operator delete(void* m) { puts("operator delete"); free(m); } class S { int i[100]; public: S() { puts("S::S()"); } ~S() { puts("S::~S()"); } }; int main() { puts("creating & destroying an int"); int* p = new int(47); delete p; puts("creating & destroying an s"); S* s = new S; delete s; puts("creating & destroying S[3]"); S* sa = new S[3]; delete []sa; } Example
Overloading new & delete for a class • the syntax is the same as overloading any other operator. • When the compiler sees you use new to create an object of your class, it chooses the member operator new over the global version. • However, the global versions of new and delete are used for all other types of objects • You can also overload new & delete operators for arrays
Operator overloading • The mechanism of giving special meaning to an operator is known as operator overloading • Although the semantics of the operator can be extended, we cannot change its syntax, the grammatical rule like the number of operands, precedence , associativity etc. • When an operator is overloaded its meaning is not lost • We cannot overload • Class member access operators (. , .*) • Scope resolution operator(::) • Size operator (sizeof) • Conditional operator(?:)
Operator overloading • Operator overloading is done with the help of operator function • return type classname:: operator op(op-arglist) { function body//TASK DEFINED } • Operator functions must be either member functions or friend functions • The process of overloading includes the steps • Create the class that defines the data type that is to be used in the overloading operation • Declare the operator function operator op() in the public part of the class. It may be either friend or member function • Define the operator function to implement the required operations
Operator overloading • basic difference between friend function and member function is • member function will not have any argument for unary operators while friend function will have one argument • Member function will have one argument for binary operators while friend function will have two arguments • vector operator +(vector); //vector addition • vector operator – (); // unary minus • friend vector operator +(vector, vector); // vector addition • friend vector operator – (vector); //unary minus
overloading unary minus class space { int x,y,z; public : void getdata(int a, int b, int c); void display(void); void operator – ( ); }; void space::getdata(int a, int b, int c) { x=a; y=b; z=c; } void space :: display(void) {cout<<x<<“ “<<y<<“ “ <<z;} void space::operator-() { x= -x; Y=-y; Z=-z; } int main() { space S; S.getdata(10, -20, 30); cout<<“S : ” ; S.display(); -S; cout<<“S :”; S.display(); Return 0; } operator overloading
Operator overloading • overloaded functions are invoked by expressions such as • op x or x op for unary operators • x op y for binary operators
class complex { float x,y; public: complex(){} complex(float real, float imag) {x=real; y=imag;} complex operator +(complex); void display(); }; complex complex :: operator +(complex c) {complex temp; temp.x = x+c.x; temp.y =y+c.y; return temp; } void complex :: display() { cout<<x<<"+j"<<y; } int main { complex C1,C2,C3; C1 = complex(2.5, 3.5); C2 = complex(1.6 ,2.7); C3 = C1+C2; cout<<"C1=";C1.display(); cout<<"C2=";C2.display(); cout<<"C3=";C3.display(); return 0; } operator overloading- overloading binary operator
Operator overloading • in binary operator overloading, the left –hand operand is used to invoke the operator function and the right hand operand is passed as an argument
class complex { float x,y; public: complex(){} complex(float real, float imag) {x=real; y=imag;} friend complex operator+ (complex, complex); void display(); }; complex operator +(complex a, complex b) {complex temp; temp.x = a.x+b.x; temp.y =a.y+b.y; return temp; } void complex :: display() { cout<<x<<"+j"<<y; } int main { complex C1,C2,C3; C1 = complex(2.5, 3.5); C2 = complex(1.6 ,2.7); C3 = C1+C2; cout<<"C1=";C1.display(); cout<<"C2=";C2.display(); cout<<"C3=";C3.display(); return 0; } Overloading binary operators using friends
Rules for overloading operators • Only existing operators can be overloaded • The overloaded operator must have atleast one operand that is of user defined type • We cannot change the basic meaning of an operator • Overloaded operators follow the syntax rules of the original operators • There are some operators that cannot be overloaded • We cannot use friend functions to overload certain operators • When using binary operators overloaded through a member function, the left hand operand must be an object of the relevant class
Where a friend cannot be used • = assignment operator • () function call operator • [] subscripting operator • -> class member access operator
Manipulation of strings using operators • Strings can be defined as class objects which can be then manipulated like the built-in types • Since the strings vary greatly in size, we use new to allocate memory for each string and a pointer variable to point to the string array • A typical string class will look like this Class string { Char *p; Int len; Public: //member functions to initialize //and manipulate strings };
Mathematical operations on strings #include<iostream.h>’ #include<string.h> Class string { Char *p; Int len; Public: String(){len=0;p=0;} String(const char * s); String(const string & s);// copy constructor ~string(){delete p;} //+ operator friend int operator+(const string &s, const string &t); //<= operator friend int operator<=(const string &s, const string &t); Friend void show(const string s); };
String :: string(const char *s) { Len=strlen(s); P=new char[len+1]; Strcpy(p,s); } String :: string(const string & s) { Len=s.len; P=new char[len+1]; Strcpy(p,s.p); } String operator+(const string &s,const string &t) { String temp; Temp.len=s.len+t.len; Temp.p=new char[temp.len+1]; Strcpy(temp.p,s.p); Strcat(temp.p,t.p); Return(temp); }
Int operator<=(const string &s, const string &t) { Int m=strlen(s.p); Int n=strlen(t.p); If(m<=n) return(1); Else return(0); } Void show(const string s) { Cout<<s.p; }
Int main() { String s1=“new”; String s2=“york” String s3=“delhi”; string t1=s1; string t2=s2; string t3=s1+s3; Show(t1); Show(t2); Show(t3); If(t1<=t3) { Show(t1); Cout<<“smaller than”; Show(t3); } Else { Show(t3); Cout<<“smaller than”; Show(t1); } Return 0; }
Type conversions • Consider the following statement v3=v1+v2;// v1 & v2 are objects of different classes • For built in types the compiler will do type conversions • But for user defined types, we must design the conversion routines • Three types of situations might arise in the data conversion between uncompatible types • Conversion from basic type to class type • Conversion from class type to basic type • Conversion from one class type to another class type
Basic to class type • Here we can use constructors to perform a type conversion from argument’s type to constructor’s class type String :: string(const char *s) { Len=strlen(s); P=new char[len+1]; Strcpy(p,s); } • The constructor builds a string type object from a char* type variable a
Example: string s1, s2; Char* name1=“ibm pc”; Char* name2=“apple”; S1=string(name1); S2=name2; • The constructors used for the type conversion take a single argument whose type is to be converted
Class to basic type • C++ allows us to define an overloaded casting operator that could be used to convert a class type data to a basic type • The general form of an overloaded casting operator function usually referred to as conversion function, is: Operator typename() { //(function statements) } • This function converts a class type to typename
Ex: vector :: operator double() { Double sum=0; For(int i=0;i<size;i++) sum=sum+v[i]*u[i]; Return sqrt(sum); } • the operator double() can be used as follows: Double length=double(v1); or Double length=v1; Where v1 is an object of type vector • The casting operator function should satisfy the following conditions • It must be a class member • It must not specify any return type • It must not have any arguments
One class to another class type • Example : Objx=objy; //object of different types • Objx belongs to class x • Objy belongs to class y • Y->source class • X->destination class • The conversion can be carried out by either a constructor or a conversion function • It depends upon whether the type conversion function to be located in the source class or destination class
Ex1: //inside class invent1 Operator invent2() { Invent2 temp; Temp.code=code; Temp.value=price*items; Return temp; } Ex2: //inside invent2 Invent2(invent1 p) { Code=p.getcode(); Value=p.getitems()*p.getprice; } It can be invoked by d1=s1; or d1=invet2(s1);
Pointers in C++ • The most important difference between pointers in C and those in C++ is that C++ is a more strongly typed language. • C doesn’t let you casually assign a pointer of one type to another, but it does allow you to accomplish this through a void*. Thus, bird* b; rock* r; void* v; v = r; b = v; • This feature of C allows you to quietly treat any type like any other type. • C++ doesn’t allow this
References in C++ • A reference (&) is like a constant pointer that is automatically dereferenced. • It is usually used for function argument lists and function return values. EX: #include <iostream> int y; int& r = y; const int& q = 12; // (1) int x = 0; // (2) int& a = x; // (3) int main() { cout << "x = " << x << ", a = " << a << endl; a++; cout << "x = " << x << ", a = " << a << endl; }
There are certain rules when using references • A reference must be initialized when it is created. (Pointers can be initialized at any time.) • Once a reference is initialized to an object, it cannot be changed to refer to another object. (Pointers can be pointed to another object at any time.) • You cannot have NULL references. You must always be able to assume that a reference is connected to a piece of storage.
References in functions • The most common place you’ll see references is as function arguments and return values. • When a reference is used as a function argument, any modification to the reference inside the function will cause changes to the argument outside the function. • you could do the same thing by passing a pointer, but a reference has much cleaner syntax
#include<iostream.h> int* f(int* x) { (*x)++; return x; // Safe, x is outside this scope } int& g(int& x) { x++; // Same effect as in f() return x; // Safe, outside this scope } int main() { int a = 0; f(&a); // Ugly (but explicit) g(a); // Clean (but hidden) }
Const references • Making the argument a const reference will allow the function to be used in all situations. • This means that, for built-in types, the function will not modify the argument, and for user-defined types, the function will call only const member functions • The use of const references in function arguments is especially important because your function may receive a temporary object. • This might have been created as a return value of another function or explicitly by the user of your function. • Temporary objects are always const, so if you don’t use a const reference, that argument won’t be accepted by the compiler.
EX: void f(int&) {} void g(const int&) {} int main() { //! f(1); // Error g(1); }
The copy-constructor • A copy constructor is used to declare and initialize an object from another object • A copy constructor takes a reference to an object of the same class as itself as an argument • The syntax is: Class-name (class-name &i){ } • We cannot pass the argument by value to a copy constructor
#include<iostream.h> class code { int id; public: code(){ } code(int a) { id=a; } code(code & x) //copy constructor { id=x.id; } void display() { cout<<id; } }; void main() { code a(100); code b(a); //copy constructor code c=a; code d; d=a; a.display(); b.display(); c.display(); d.display(); } Example of copy constructor
Pointers to members • A pointer is a variable that holds the address of some location. • You can change what a pointer selects at runtime, and the destination of the pointer can be either data or a function. • The C++ pointer-to-member follows this same concept, except that what it selects is a location inside a class • EX: struct Simple { int a; }; int main() { Simple so, *sp = &so; sp->a; so.a; }