FAQ 24.08 Are there techniques that increase the
|
FAQ 24.09 How should the assignment operator in a derived class behave?
An assignment operator in a derived class should call the assignment operator in its direct base classes (to assign those member objects that are declared in the base class), then call the assignment operators of its member objects (to change those member objects that are declared in the derived class). These assignments should normally be in the same order that the base classes and member objects appear in the class's definition. An example
class Base {
public:
Base& operator= (const Base& b) throw();
protected:
int i_;
};
Base& Base::operator= (const Base& b) throw()
{
i_ = b.i_;
return *this;
}
class Derived : public Base {
public:
Derived& operator= (const Derived& d) throw();
protected:
int j_;
};
Derived& Derived::operator= (const Derived& d) throw()
{
Base::operator= (d);
j_ = d.j_;
return *this;
}
Typically, a Derived::operator= shouldn't access the member objects defined in a base class; instead it should call its base class's assignment operator. Nor should a Base::operator= access member objects defined in a derived class (that is, it usually shouldn't call a virtual routine, like copyState() , to copy the derived class's state).
If a
Base::operator=
tried to copy a derived class's state via a virtual function, the
For example, suppose
Base
defines
Base::operator= (const Base& b)
, and this assignment operator calls virtual function
copyFrom(const Base&)
. If the derived class
Derived
At best, this is a waste of CPU cycles because it reassigns the Derived member objects. At worst, this is semantically incorrect, because special changes made during Derived::copyFrom(const Base&) may get wiped out when the Derived member objects are subsequently assigned by Derived::operator= (const Derived&) . |
FAQ 24.10 Can an ABC's assignment operator be virtual ?An ABC's assignment operator can be virtual only if all derived classes of the ABC will be assignment compatible with all other derived classes and if the developer is willing to put up with a bit of extra work. This doesn't happen that often, but here's how to do it. Classes derived from a base class are assignment compatible if and only if there's an isomorphism between the abstract states of the classes. For example, the abstract class Stack has concrete derived classes StackBasedOnList and StackBasedOnArray . These concrete derived classes have the same abstract state space as well as the same set of services and the same semantics. Thus, any Stack object can, in principle, be assigned to any other Stack object, whether or not they are instances of the same concrete class.
If all classes derived from an ABC are assignment compatible with all other derived classes from that ABC, there are two choices: when a
It is
The other choice is to make assignment work correctly when the user has a reference to the base class. This is done by making the base class's assignment operator
public:
and
virtual
. This approach allows any arbitrary
Stack&
to be assigned with any other
Stack&
, even if the two
Stack
objects are of different derived classes. The base class version of the assignment operator must be overridden in each derived class, and these
class Stack {
public:
virtual ~Stack() throw();
virtual void push(int elem) throw() = 0;
virtual int pop() throw() = 0;
virtual int getElem(int n) const throw() = 0;
virtual Stack& operator= (const Stack& s) throw();
protected:
int n_;
};
Stack::~Stack() throw()
{ }
Stack& Stack::operator= (const Stack& s) throw()
{ n_ = s.n_; return *this; }
void userCode(Stack& s, Stack& s2)
{ s = s2; }
The overridden assignment operator and the overloaded assignment operator in a derived class, such as the
StackArray
class that
class StackArray : public Stack {
public:
StackArray() throw();
virtual void push(int x) throw();
virtual int pop() throw();
virtual int getElem(int n) const throw();
virtual StackArray& operator= (const Stack& s) throw();
//override
StackArray& operator= (const StackArray& s) throw();
//overload
protected:
int data_[10];
};
StackArray::StackArray() throw()
: Stack() { }
void StackArray::push(int x) throw()
{ data_[n_++] = x; }
int StackArray::pop() throw()
{ return data_[--n_]; }
int StackArray::getElem(int n) const throw()
{ return data_[n]; }
// Override:
StackArray& StackArray::operator= (const Stack& s) throw()
{
Stack::operator= (s);
for (int i = 0; i < n_; ++i)
data_[i] = s.getElem(i);
return *this;
}
// Overload:
StackArray& StackArray::operator= (const StackArray& s) throw()
{
Stack::operator= (s);
for (int i = 0; i < n_; ++i)
data_[i] = s.data_[i];
return *this;
}
int main()
{
StackArray s, s2;
userCode(s, s2);
}
Note that the override (
StackArray::operator= (const Stack&)
) returns a
StackArray&
rather than a mere
Stack&
. This is called a
|