Assignment Operator - More C++ ramblings
Assignment Operator
Assignment operators perform the operation of assigning(copying) one object/entity to another EXISTING entity. In C++ assignment operator is available for built-in types and also for user-defined types. What makes them more interesting in C++ is that the users can overload/override assignment operators for user defined types.
The assignment operator is very similar to the Copy constructor in C++. The difference begin that operator= is invoked on existing(already created) objects. However copy constructor invoked at the time of object creation.
In the subsequent section we discuss the properties and caveats of the assignment operator. That will be followed with a short FAQS section.
Properties
Caveats
FAQs
Properties
Like all other language facilities, assignment operators have certain properties:
- Compiler generates one if not specified
- Shallow copy by default
- Syntax of a member operator=
If the programmer does not specify an assignment operator the compiler automatically generates a default assignment operator for that class.
Programmer can specify the assignment operator
If the programmer provides a class level assignment operator, the compiler does not generate the assignment operator. This gives the programmer the freedom to modify the default operation of the assignment operator. However the programmer must ensure that semantics of the programmer defined assignment operator comply with the built-in types.
The default compiler generated constructor uses shallow copy. It does memberwise copy. So if a particular member is a pointer variable the pointee is shared by both the objects as a result of the assignment. However the programmer can overload/redine the assignment operator to provide deep copy explicitly. The assignment operator and copy constructor should more less have the same behaviour.
Let us try and declare the assignment operator for a String class
class String
{
....
String& operator=(const String& rhs);
....
};
It is important not to deviate from the above mentioned declaration of operator=. For example:
* If we define the operator= of type void. However this prevents cascading. The language garauntees the following facility for built-in types.
class String { .... };
String str1 = String("Hello World");
String str2, str3;
.....
str3 = str2 = str1; // This will not be possible if the
// return type is void
* If we return the operator= by value or if we return a const reference it will allow the cascading but something silly like following will fail.
.....
(str3 = str2) = str1; // Something silly like this will fail. But the built -
// in types support this type of an operator
The above line of code is totally useless. But since built-in types allow such an operation, and your class does not support it the contract established by the language will be violated.
Caveats
The assignment operator in C++ is shipped with an army of caveats - ofcourse hidden :-).
- Self Assignment
- Derived class operator=()
- Member function or File-scope function
- Object slicing
- Virtual Assignment operator
- Templates and assignment operator
- Old compilers and Default base class assignment operators
C++ allows something useless as this:
OR
...
String a;
a = a;
.....
void foo (String& a, String& b)
{
...
a = b;
...
}
...
// In client code
String str;
foo(str, str);
...
The above pieces of code illustrates self assignment. The operator= must address this case. If not, bad things can happen.
Consider the following operator= for a String class not doing self assignment
class String
{
....
public:
String& operator=(const String& rhs);
....
private:
char* str;
};
.....
// Bad assignment operator
String& operator=(const String& rhs)
{
delete[] str;
int len = strlen(rhs.str) + 1;
str = new String[len]
strcpy(str, rhs.str);
}
...
// In client code
String string ;
string = string; // Bad. The "string" pointed to by str
// will be deleted first. And program
// will crash when calling strlen
...
Here is an example of a good assignment operator which addresses self-assignment.
String& operator=(const String& rhs)
{
if ( this == &rhs)
return *this;
delete[] str;
int len = strlen(rhs.str) + 1;
str = new String[len]
strcpy(str, rhs.str);
return *this;
}
One of the very important things to realize when defining is a derived class operator= is that we are overloading and hiding the base class operator=.
The derived class has a different scope from the base class scope. The derived class scope is more or less similar to the a nested class scope. Hence, any identifier( a function name or a variable name) re-defined/declared in the derived class, with same name as an identifier in the base class, will hide the identifier in the base class. Again this is primarily because, derived class has a DIFFERENT scope. NOT because the base class operator= is not inherited.
All of the above story is important to us because every programmer must know that when providing a assignment operator for the derived class, the base class operator= must be explicity called since it is not called automatically unlike constructors. First lets see what happens if we do not do that:
class Base
{
public:
Base& operator=(const Base& rhs)
{
// This is more or less redundant but
// doing it for consistency sake.
if(this == &rhs)
return *this;
x=rhs.x;
return *this;
}
private:
int x;
};
class Derived : public Base
{
public:
// Bad operator
// The base class operator is not invoked automagically
// Consequently, the base class part will not be copied
// Only the derived part will be copied from rhs.
Derived& operator=(const Derived& rhs)
{
// This is more or less redundant but
// doing it for consistency sake.
if(this == &rhs)
return *this;
y=rhs.y;
return *this;
}
private:
int y;
}
...
// Client code
Derived d1( 3,4 );
Derived d2 ( 6,7);
d2 = d1;
// This will print x = 6 ;; y = 4 !!!
std::cout << d2 << std::endl;
This can be easily fixed by the following code
....
Derived& operator=(const Derived& rhs)
{
if(this == &rhs)
return *this;
Base::operator=(rhs);
.......
}
....
Make it a member function.
Consider the following piece of code
class A {.... };
...
class B: public A {....};
// Client code
.....
A* a;
B b;
B c = B(blah1, blah2);
a = &b;
*a = c; // This is going to call A::operator=() unless
// the operator= in A is virtual and overridden in B
// Since the operator= is NOT virtual there is static binding
// and the operator= of the static type (A in this case)
// will be invoked.
// Coming soon
// Coming soon
// Coming soon
FAQs
* Why should the return type of an assignment operator be Type&
// Coming soon
* When should I provide an assignment operator
// Coming soon
0 Comments:
Post a Comment
<< Home