OPERATOR OVERLOADING

Download Report

Transcript OPERATOR OVERLOADING

OPERATOR OVERLOADING
• Closely related to function overloading is - operator
overloading. In C++ you can overload most operators
so that they perform special operations relative to
classes that you create.
• After overloading the appropriate operators, you can
use objects in expressions just like built in data
types. An operator function defines the operations
that the overloaded operator will perform relative to
the class upon which it will work.
• It means performing an operator to work on
operands of types it has not yet been designed to
operate. e.g. ‘+’ will work on char, int, float, double.
It will not work on our ‘String’ class objects
s1 = s2 + s3; //will not compile
• Operator overloading functions are either member
functions or friend functions of that class whose
objects are intended as operands of the overloaded
operator.
• The names of the operator-overloading functions
are composed of the keyword operator followed by
the symbol of the operator being overloaded.
< ret_type> operator <op> (<arg_list>) ;//prototype
< ret_type> <class_name>::operator<op>(<arg_list>)
{ // function body }
• Member functions that overload operators can be
private, protected or public
• A friend function that overloads a given operator:
friend<ret_type>operator<op>(<arg_list>);//prototyp
< ret_type> operator<op>(<arg_list>)
{ // function body }
• Example of string class – member function
class String
{ public :
String operator + (const String &) const;
//prototype
//rest of class String };
String String :: operator +(const String & ss ) const
{ // function body } //definition
• Then we can use the string object as
String s1, s2, s3 ; //String class objects
s3 = s2 + s1 ; //call to operator overloading function
• If it is a friend function –
friend String operator + ( const String &,
const String &) ; // prototype
String operator +(const String&ss1, const String&ss2)
{ // function body }
//definition
• How the compiler interprets the operatoroverloading functions ?
s3 =s2 + s1; is interpreted as s3=s1.operator+(s2);
if it is a member function
• If it is a friend function then
s3 = operator + (s1, s2) ;
• Compiler does not say that invalid operands have
been passed to the operator!
• The operator overloading functions can also be
called directly from with in the application
programs – the way the compiler interprets it !
s3=s1.operator+(s2); or s3 = operator + (s1, s2) ;
• We must note that only the name of the operator
overloading function is unusual. Otherwise, they
are implemented just like ordinary member, nonmember or friend functions.
• Why friend functions used to overload operators?
• Let us consider two classes- class A (we defined)
and class B(an existing class or an intrinsic datatype)
• We realize that for some reason only object of class
A will be added to an object of class B to get
another object of class A :
a2 = b1 + a1;
• Object of class B will not be added to object of
class A and b1 will not appear on right of ‘+’
• We have no means of modifying the definition of B
• If we define the member function as followsclass A { public : A operator + ( const B & ) ; } ;
//will not compile.
• The compiler will interpret the statement
a2 = b1 + a1; first as a2 = b1 . Operator +(a1) ;
and then as a2 = operator + ( b1 , a1 );
• The prototype of the member function satisfies
neither of the two interpretations and compiler
will throw an error.
• Declaring the operator overloading function as a
friend function with an object of class B as the
first formal argument solves this problem
friend A operator + ( const B &, const A &);//PT
A operator + ( const B & bb, const A & aa )
{ // function body } // defintion
• The compiler throws an ambiguity error if both
member function and friend functions are used to
overload an operator, because both of them will
satisfy calls to the overloaded operator.
Overview of overloading unary and binary operators
• Member functions that overload unary operators
take no operands, because the calling object is
passed as an implicit parameter
• Friend functions will take one parameter, since the
function is invoked without the object reference
• Similar explanation can be extended in case of
overloading binary operators – in member and
friend functions
• Why are operators overloaded?
• The operator-overloading functions can be easily
substituted by member functions or friend
functions with ordinary but meaningful and
relevant names :
String add(const String & ); //prototype
String String :: add(const String & ss ){ }//definition
String s1, s2, s3 ; s3 = s1 . add (s2) ;
• This is a substitute to the ‘addition’ operator (+)
• Operator overloading becomes mandatory under
the following circumstances :
(i) Objects of the class acquire resources
dynamically during runtime and no two objects
should share the same copy of the resource:
consider the String class – String s1(“abc”), s2;
s2 = s1 ; now both cStr will point to the same
block which is undesirable. Hence we defined a
suitable copy constructor. The same factors
dictate that a suitable function to overload the
assignment operator be defined for the ‘String’
(ii) Objects of the class acquire some resources
dynamically during runtime and no two objects
should share even different copies of the
resource:
• Imagine that there is a class whose objects should
not share even separate copies of dynamically
allocated resources. This means even statements
o1 = o2 ; // should not compile.
• The solution is quite simple. We just declare the
function to overload the assignment operator in the
private section of the class. Any use of the assignment operator with in a non-member function will
launch a call to this function. Since the function is
private, a compile-time error is thrown. If a member
or friend function uses the assignment operator,
then it can not be prevented. But, it can be
prevented by not defining the function to overload=
(iii) Objects need to be passed as parameters in
function templates and the operators being used on
template class objects with in the template
functions should work in the same way on the
objects of the class : discussed later
(iv) The default action of the dynamic memory
management operators( new and delete ) are
unsuitable for the class being designed :
The new operator throws an exception, if it fails to
allocate memory by default. This may be
undesirable to the class designed. The class
designer may need to call a member function for
this. Only overloading ‘new’ fulfill the need
(v) Change in the implementation of the class forces
an undesirable change in its interface in turn
necessitating the rewriting and recompiling of the
application programs
(vi) Overloading provides a better readability of code.
o2 = ++o1; better than o2 = o1.pre_fix_increment();
Rules for operator overloading
• New operators can not be created : creating new
operators (such as **) will produce compile time
error. void operator ** ( ); //illegal
• Meaning of the existing operator can not be
changed : any operator overloading function should
take at least one operand of the class of which
it is a member or friend. Thus it is not possible to
change the manner in which an existing operator
works on operands of fundamental types. In
member functions it is automatically enforced. In
case of friend functions, the library programmer
needs to take extra care. Illegal attempt friend int operator + (int, int); // error
• Some of existing operators can not be overloaded :
:: (scope resolution)
. (member selection)
.*(member selection through pointer to member)
?: (conditional operator) sizeof (size of values)
typeid (finding the type of object pointed at)
• Some operators can be overloaded using non-static
member functions only: = (assignment operator)
( ) (function operator) [] (subscripting operator)
-> (pointer to member access operator)
these can not be overloaded using friend or static fn.
• Number of arguments that an existing operator
takes can not be changed : statement like
void operator / () ; //causes compile time error
• overloaded operators can not take default
arguments : void operator / ( int=0 ) ; //causes
compile time error
• It is highly imprudent to modify the values of the
operands that are passed to the operator
overloading functions.
• Considering the String class- (will not compile)
String operator + (String &); // prototype
String String :: operator + (String & ss) //definition
{ …. ; this->cStr =NULL; //bug: LH param changed
ss->cStr = NULL; // bug: RH param changed }
String operator + (String & const) const ;
//correct one
the const keyword prevent the bugs and compile.
Overloading the various operators:
• Increment and decrement operator:
Considering the ‘Distance’ class, we can overload
the increment operator for objects of the class. If d1
and d2 are objects, then d2 = ++ d1; is
interpreted as d2 = d1. operator ++( ) ;
Distance operator + + ( ) ; // prototype
Distance Distance :: operator ++ ( ) // fn. definition
{ return Distance (++iFeet, fInches); }
• Above should not be a constant member function
• In the function first the iFeet data member of the
calling object gets incremented
• The explicit call to the constructor creates a
nameless object by passing the incremented value
of ‘iFeet’ and unaltered value of ‘fInches’
• The operator overloading function returns the
nameless object thus constructed. If the call is on
RHS of assignment operator, the returned object
will be copied to the object on the left.
• If we write d2 = d1 + +; d1 should get incremented
after copying to d2.
• The compiler interprets as d2 = d1 . Operator ++(0);
• It implicitly passes zero as parameter to the call to
the overloading function when the postfix notation
is used. If it finds a prototype of exact match,
it compiles without warnings or errors.
• The compiler first looks for a function with an
integer as a formal argument provides us with a
solution. Formal parameter is a dummy(nameless)
class Distance
{ public : Distance operator + +( ) ;
//prefix
Distance operator + +(int ) ; //postfix
/* rest of class Distance */
};
Distance Distance :: operator + +( ) //prefix
{ return Distance (++iFeet, fInches) ; }
Distance Distance :: operator + +( int ) //postfix
{ return Distance (iFeet+ +, fInches) ; }
• If we provide an operator overloading function for
the ‘increment’ operator in prefix notation, we
must provide one for postfix notation also.
• For ‘decrement’ operator – prototype & definitions
Distance operator - -( ) ; //prefix
Distance operator - -(int ) ; //postfix
Distance Distance :: operator - -( ) //prefix
{ return Distance (- -iFeet, fInches) ; }
Distance Distance :: operator - -( int ) //postfix
{ return Distance (iFeet- -, fInches) ; }
Overloading the unary minus and plus operator
class A
{ int x ;
public : A (int = 0 ) ;
A operator - ( ) ; } ;
A A :: operator – ()
//member function
{ return A ( -x ); }
• For friend function
friend A operator – ( const A & ) ; //prototype
A operator – (const A & Aobj )
//definition
{ return A(- Aobj. x ) ; }
Overloading the Arithmetic operators
• In a member function, the right hand side operand
of the operator appear in list of formal arguments
• In a friend function, both operands will appear
class Distance //overloading thro member function
{ int iFeet; float finches;
public: Distance (const int = 0, const float = 0.0 );
void setFeet(const int=0);
int getFeet() const;
void setInches(const float=0.0);
float getInches() const;
Distance operator + (const Distance) const };
Distance Distance :: operator + (const Distance dd1)
{
const
return Distance ( iFeet + dd1.iFeet ,
fInches + dd1.fInches );
}
• now we can create objects and call function
Distance d1(5,8) , d2(4,6) , d3;
d3 = d1 + d2 ;
• If we pass 2nd parameter a float value then above
code will not work because d3 = d1 + 4.5 ; will be
interpreted as d3 = d1. operator + (4.5) ; and
compiler will throw an error. To solve this problem
we need to introduce a suitable constructor that
converts from float to ‘Distance’ type
Distance :: Distance (const float p)
{ iFeet = (int) p; fInches = ( p – iFeet ) * 12 ; }
• If the left hand side operator is of float type, then it
should be replaced with a friend function.
friend Distance operator + (const Distance,
/*prototype */
const Distance ) ;
Distance Distance :: operator + (const Distance dd1,
{
const Distance dd2)
return Distance ( dd1.iFeet + dd2.iFeet ,
dd1.fInches + dd2.fInches );
}
• This will tackle all the three conditions of d1 & d2.
Now if both operands are float type values - then
• The operator overloading mechanism will not be
invoked, instead values get added to each other.
d3 = 4.75 + 3.25 ;
will turn into
d3 = 8.0 ; and
the constructor that takes float value as parameter
and initializes the object with it will be called. Such
a constructor is called implicit constructor.
Overloading the Relational operators
• These are binary operators. The syntax is similar to
that of the arithmetic binary operator. Considering
the class Distance, the function to overload ‘>’
enum bool {false, true};
class Distance { …
bool operator > (const Distance )const;//prototype};
bool Distance::operator>(const Distance dd1)const
{ if(iFeet*12 + fInches > dd1.iFeet*12 + dd1.fInches)
return true;
return false; } d1>d2 ; d1>4.5 ;
• This will work if RHS operator is a ‘Distance’ object.
If it is a float type value, then it will not compile.
• Introducing a suitable constructor will solve the
problem. What about 4.75 >d1 ?
• We need to replace it with a friend function
bool operator>(const Distance dd1,const Dist dd2)
{ if(dd1.iFeet*12 + dd1.fInches > dd2.iFeet*12 +
dd2.fInches) return true;
return false; }
• All four possibilities as in ‘+’ operator
Overloading Assignment operator
• It is a binary operator and must be overloaded by a
non-static member function only. //prototype
class_name & operator = (const class_name &);
• By default, the compiler generates the function to
overload the ‘assignment’ operator if the class
designer does not provide one. It carries out a
simple member-wise copy.
class A { public : A & operator =(const & ); };
A & A :: operator = (const A & rhs)
{ * this = rhs;
return this; }
• In most cases, the default assignment operator is
sufficient. For classes that acquire resources
dynamically, default causes problem.
• String s1(“abcd”);
String s2= s1;
• The conclusion is that the assignment operator
must be defined for a class whom the copy
constructor is defined. Example
String (const String &); // copy constructor
String & operator = (const String &);
String String :: operator = (const String & ss)
{ if this != &ss)
{if(cStr != NULL) {delete[] cStr; cStr = NULL;len=0; }
if ( ss.cStr != NULL)
{ len = ss.len;
cStr = new char[len + 1];
strcpy(cStr, ss.cStr) ; } }
return *this; }
• If LHS.cStr =NULL and RHS.cStr =NULL
If LHS.cStr =NULL, then the first inner ‘if’ fails and
the corresponding if block does not execute. If
RHS.cStr =NULL then the second inner ‘if’ fails and
the block does not execute. The entire function
does not do anything except that it returns the
calling object by reference. The value of LHS
• If LHS.cStr =NULL and RHS.cStr !=NULL
If LHS.cStr =NULL, then the first inner ‘if’ fails and
the corresponding if block does not execute. If
RHS.cStr =!NULL then the second inner ‘if’
(if(ss.cStr!=0)) succeeds and the corresponding
‘if’block executes. It does the following: (i) correctly
sets the value of len member of the calling object to
be equal to the length of the memory block that will
hold a copy of the string at which ‘cStr’ member of
the RHS object is pointing. (ii) allocates just enough
memory to hold a copy of the string at which the
cStr member of the RHS object is pointing and
makes cStr of LHS object point at it
(iii) Copies the string at which the cStr of RHS object is
pointing in to the memory block at which the cStr of
LHS object is pointing.
• If LHS.cStr !=NULL and RHS.cStr =NULL
then
the first inner ‘if’ succeeds and the corresponding if
block executes. It deallocates the memory the block
at which cStr of LHS object points, sets its value to
NULL and sets the value of the ‘len’ member of LHS
object to ‘0’. If RHS.cStr=NULL then the second
inner ‘if’ fails and the corresponding if block does
not execute.
• If LHS.cStr !=NULL and RHS.cStr !=NULL
then
the first inner block succeeds and executes.
It deallocates the memory the block at which
cStr of LHS object points, sets its value to
NULL and sets the value of the ‘len’ member
of LHS object to ‘0’. If RHS.cStr!=NULL then
the second inner ‘if’ succeeds and the
corresponding if block executes. It does the
required things.
• The preceding function accepts the argument
as a const reference and returns the calling
object by reference – to test and safeguard
against self-assignment. The if condition
‘this == &ss’ tests to find out whether an
object is being equated with itself or not.
• An object may be get equated as
String s1 ; s1 = s1; or
String s1; String &s2 = s1; s2 = s1;
• Each of the above will cause an extension of the
function to overload the assignment operator. In
each of the cases ‘if’ evaluates to true. For such
circumstances the operator overloading function
has been designed to remain unexecuted. This is
necessary because in case of self assignment no
action is necessary. Unnecessary allocationdeallocation should be avoided.
• The function has been designed to return by
reference – to prevent chaining operation from
being inefficient, that is, to ensure an efficient
execution of statements such as :
String s1, s2, s3; s3= s2 = s1; interpreted as
s3.operator = (s2.operator = (s1) );
suppose s2= s2 = s1; suppose it is returned by value
and not by reference. Then value of s2 is created in
stack and its address is different from that of s2.
When the assignment operator executes for the
second time, reference variable ‘ss’ refers to this
copy and not to s2 itself.
• When the library programmer wants to overload
the ‘assignment’ operator and may not want to
share even different copies of the same object.
• A derived class object can be assigned to a base
class object but the reverse is not true.
A A1 ; B B1; A1=B1; //OK
B1= A1; //ERROR!
the data members exclusively in B1 will remain
unchanged and may no longer match with rest of
the members of ‘B1’. Hence the compiler prevents!
• If the class designer desires, he may provide an
assignment operator function to the derived class.
public: B& operator = (const A&); //to enable B1=A1
The insertion and Extraction operators:
for insertion operator :
class A
{ public : //prototype
friend ostream & operator <<(ostream &,const A&);
//rest of class A };
ostream & operator<<(ostream & dout, const A & AA)
{// rest of the function
return dout; }
The statement cout << A1; is interpreted as
operator << (cout, A1);
(without const)
Same syntax for extraction also ( ‘din’ and ‘>>’ )
• These are overloaded using friend functions
• The objects of the classes ‘istream’ and ‘ostream’
are passed and returned by reference – because the
copy constructor and the assignment operator have
been declared as protected members in both the
classes. This prevents two objects unnecessarily
sharing even different copies of the same stream.
ostream dout = cout ;; // ERROR
istream din ; din = cin ; //ERROR
• The compulsion to return by reference
if the object is returned by value, then a separate
object is created in stack. Then call to the copy
constructor.
However, the copy constructor is a protected
member! Therefore the object must be returned by
reference and not by value.
• But why should the object be returned? Can it be friend void operator << (ostream &,const A &) ;
void operator << (ostream & dout, const A & AA)
{//definition of the function } // yes!
how will we chain the operator?
cout<<A1<<A2;
will be interpreted as
operator << (operator << (cout, A1) , A2) ;
if the inner nested call returns ‘void’ instead of
‘ostream &’ how will the outer call execute?
• The << and >> are overloaded to achieve data
abstraction – a complete independence between
the interface and implementation. The signatures
and return types of member functions do not
change even if the data members with in the class
do. Changes in the internal representation of data
members of a class do not force its client programs
to change. These need not be aware of these.
• Considering the class ‘String’ and getString() – the
function will return a char* (string is stored in a null
terminated memory block of characters). If the
string is stored in wide characters, then statement
cout << s1.getString()<< endl; will no longer work.
• If the data type changes, the existing clients fail!
• If the statement is written as
cout<<s1<<endl;
then the responsibility of displaying the data is
shifted to the object itself.
• In the actual programming world, libraries are
provided as DLL. They do not form the part of the
executables physically. They exist separately.
Operator overloading, together with DLLs, enables a
library programmer in achieving complete data
abstraction.
• A friend function as an alternative to operator
overloading : print(cout,s1);
friend ostream& print(ostream&,const String&);
New and Delete operators
• The behavior of these operators can be altered for
operands of specific class types. If the operators are
overloaded for specific class, then the functions
that overload are called when the class type is
passed as a parameter to these operators.
Otherwise the global new and delete operators are
called. If ‘new’ is overloaded in class ‘X’ and not in
‘Y’ , then X* Xptr = new X;
//overloaded new
where as Y * Yptr = new Y;
// global new
static void * operator new(size_t); //prototype
void *<class_name> :: operator new(size_t size)
{ //definition of function }
static void * operator new[](size_t); //prototype
void *<class_name> :: operator new[](size_t size)
{ //definition of function – for array }
static void operator delete(void*, size_t);
//prototype for delete
void <class_name> :: operator delete(void * p,
{ //definition of function }
size_t size)
static void operator delete[](void*, size_t);
//prototype for delete
void <class_name> :: operator delete[](void * p,
{ //definition of function }
size_t size)
• The new and delete functions must be static. Their
prototypes need not be prefixed with ‘static’. The
compiler treats these as ‘static’.
• The return type of the ‘new’ must be ‘void *’. The
returned value is the address of the memory block.
It should take at least on formal argument ‘size_t’.
class A
{ int x; public : static void * operator new(size_t); } ;
#include”A.h”
void * A :: operator new(size_t size)
{ cout<< sizeof(A)<<endl; cout<<size <<endl;
void *p = ::operator new(size); return p; }
#include “A.h”
void main()
{ A * Aptr = new A; } // output 4 4
• Class designer will insert necessary code to
overload ‘new’ operator and to allocate required
amount of memory and then to return the address
of the captured memory.
• The type ‘size_t’(essentially an unsigned integer) is
a defined type capable of containing the largest
piece of memory that can be allocated. ‘size’ will
contain the number of bytes needed to hold the
object being allocated.
• The return type in ‘delete’ must be void, as it does
not return anything. Its first argument should be of
type void*. The address of the memory being
deleted is passed to it. The second argument is
‘size_t’.
class A
{ int x; public : static void operator delete (
void * const, const size_t);
};
#include”A.h”
void A :: operator delete (void * const p, const
size_t size)
{ cout<< p<< endl ; cout<< sizeof(A)<<endl;
cout<<size <<endl;
::operator delete(size); }
#include “A.h”
void main()
{ A * Aptr = new A; cout<<Aptr<<endl; delete Aptr;}
output :OxCCCCCC OxCCCCCC 4
4
• ‘new’ and ‘delete’ functions are static by default.
The compiler places a call to constructor
(destructor) immediately after(before) the call to
‘new’ (‘delete’) operator.
A * Aptr = new A ;
is translated as
A* Aptr =A :: operator new(sizeof(A));
A :: A(Aptr) ;// nameless object created and
constructor called for that object.
• The statement delete Aptr; is translated as
A:: ~A(Aptr); //destructor called for nameless object
A:: operator delete (Aptr, sizeof (A) ); //nameless
object destroyed
• Consider the class ‘String’, cStr is the pointer data
member. If access is allowed to the private data
member in the ‘new’ operator function, the class
designer may accidentally allocate memory in heap
and make cStr point at it. Now when the
constructor is called, a memory leak will occur. So,
to prevent this, first any allocated memory must be
deallocated and cStr should be assigned with NULL.
• The drawback of this is – the above will be true, if
the object is created with ‘new’ operator. If the
objects are created in the normal fashion : A A1;
then ‘cStr’ containing some value, does not mean
that, it is definitely pointing to a valid memory block
of memory. ‘cStr’ is simply a rouge pointer and
using ‘delete’ on this will lead to a run-time error.
• For such reasons, c++ compiler prevents access to
non-static data members by treating the operator
‘new’ and ‘delete’ functions as static.
• Values are passed to the constructor in the usual
way even after the ‘new’ operator is overloaded.
A* Aptr = new A(10,20);
is translated as
A* Aptr =A:: operator new; A:: A (Aptr,10,20);
• In order to deallocate the correct amount of
memory, the ‘delete’ operator must know the size.
The compiler prefixes the size of the memory
allocated by ‘new’. The memory used for this
becomes a bottleneck in applications where
memory is critical.
• If ‘new’ allocates an array of objects, then one
memory slice will be sufficient to store the size.
• The class designer can ensure that when ‘new’
called for the first time, a memory block to hold a
large number of objects gets allocated and the
address of the memory block gets returned. The
subsequent calls will return address of next block.
• When once the memory pool gets exhausted,
another pool will get allocated.
• ‘new’ and ‘delete’ functions get inherited.
class A
{ int x;
public: void setx(const int = 0); int getx () const ;
static void operator new(const size_t);
static void operator delete (void * const
size,const size_t);};
class B : public A
{ int y ; public : void sety(const int=0);
int gety(); };
void main ()
{ B * Bptr01 = new B;
delete Bptr01; }
output : operator new of class A called
operator delete of class A called
• How this can be a problem?
void main ()
{ B * Bptr01 = new B;
B * Bptr02 = new B;
Bptr02->setx(20);
cout << Bptr02->getx()<< endl;
Bptr01->sety(10); cout << Bptr02->getx()<< endl; }
• In order to neutralize the effect of inheritance, the
size of memory block being targeted for allocation
and deallocation should be compared with the size
of the class for which the operator is being
overloaded. If these two do not match, then the
global ‘new’ or ‘delete’ should be called.
Overloading the subscript operator:
class <class_name>
{ public : <return_type>operator[] <param_list>;};
<return_type> <class_name> operator[] <param_list>
{// statements }
• The function must be a non-static member of the
class.
class String
{ public:
char& operator[] (const int) ;// for non-const objects
char& operator[] (const int) const;//for const objects
};
char& String ::operator[] (const int p)
{ return cStr[p] ;}
char& String ::operator[] (const int p) const
{ return cStr[p] ;}
• The function returns the corresponding element by
reference. Because the subscript operator might be
used on LHS of assignment operator also.
•
•
•
•
s[1] = ‘x’ ; // assign ‘x’ to second char of s
If you use the first function for the constant object
like const String s(“abcd”); cout <<s[1];//ERROR .
Hence we introduced a second function.
Also s[1] = ‘x’; //compiles but should not
To prevent this we should make the return type
reference ‘const’.
The formal argument of the function that overloads
the ‘subscript’ operator can be of any type such as
int, char, float, double.
Overloading pointer-to-member(->) operator
(smart pointers):
• It is a postfix unary operator. It is overloaded as
#include”String.h”
class StrPtr
{ String * p;
public : StrPtr (String & ss) { p= ss ; }
String * operator -> () {return p; }
};
• These can be designed to inevitably point at valid
objects. The ordinary pointers have to be explicitly
initialized, tested for validity before every use.
• Now the client can create the object of the classString s1(“abc”); StrPtr p(s1); p->setString(“def”) ;
• In case of ordinary pointers, there is no guarantee
that the pointer being used is pointing at valid block
of memory.
• If you try to initialize an object of class ‘StrPtr’
results in compile time error
StrPtr p; // error; no zero-arg constructor
• void f1(StrPtr p)
{ p-> setString(“abc”); //p is definitely valid }