12. Предефиниране на операции и управление на паметта

  Предефиниране на операции.
    Даване на ново значение на операция се нарича предефиниране (operator overloading). Операциите се предефинират за аргументи - обекти от даден клас, като типът на поне един от аргументите на бинарна операция трябва да е клас.

// overload.cpp
#include <iostream>
using namespace std;

#include "ccc_time.h"

/* Предефиниране на бинарна операция - с аргументи от клас Time и стойност от тип long */
long operator-(Time a, Time b)
/* ЦЕЛ: пресмята броя на секундите между два момента от време
   ПОЛУЧАВА: a, b - два момента от време
   ВРЪЩА: броя на секундите между a и b
*/
{  return a.seconds_from(b);   }

/* Предефиниране на бинарна операция + с аргументи от клас Time и тип long и стойност от клас Time */
Time operator+(Time a, long sec)
/* ЦЕЛ: пресмята момент от време, отдалечен на зададен брой секунди
   ПОЛУЧАВА: a - момент от време
   ВРЪЩА: момент от време, отдалечен на зададения брой секунди
*/
{  Time r = a;
   r.add_seconds(sec);
   return r;  }

/* Предефиниране на бинарна операция == с аргументи от клас Time и стойност от тип bool */
   bool operator==(Time a, Time b)

/* ЦЕЛ: сравнява два момента от време
   ПОЛУЧАВА: a, b - два момента от време
   ВРЪЩА: true ако те са равни и false ако не са равни
*/
{  return a.seconds_from(b) == 0;  }

/* Предефиниране на бинарна операция != с аргументи от клас Time и стойност от тип bool */
   bool operator!=(Time a, Time b)

/* ЦЕЛ: сравнява два момента от време
   ПОЛУЧАВА: a, b - два момента от време
   ВРЪЩА: true - ако те не са равни
*/
{  return a.seconds_from(b) != 0;  }

/* Предефиниране на бинарна операция << с аргументи от клас ostream и клас Time и стойност от тип ostream */
   ostream &operator<<(ostream &out, const Time &a)
/* ЦЕЛ: отпечатва обект от тип Time
   ПОЛУЧАВА: out - изходен потокa - момент от време
   ВРЪЩА: входния параметър out
*/
{  out << a.get_hours() << ":";
   if (a.get_minutes() < 10) out << "0";
   out << a.get_minutes() << ":";
   if (a.get_seconds() < 10) out << "0";
   out << a.get_seconds();
   return out;    }

/* Предефиниране на унарна операция - префиксна форма на ++ с аргумент от клас Time и стойност от клас Time */
Time operator++(Time &a)
/* ЦЕЛ: добавя към момент от време 1 секунда
   ПОЛУЧАВА: a - момент от време
   ВРЪЩА: новият момент от време и променя a
*/
{ a = a + 1; return a; }

/* Предефиниране на унарна операция - постфиксна форма на ++ с аргумент от клас Time и стойност от клас Time */
Time operator++(Time &a, int dummy)
/* ЦЕЛ: добавя към момент от време 1 минута
   ПОЛУЧАВА: a - момент от време
   ВРЪЩА: непромененият момент от време и променя a
*/
{ Time b = a;
  a = a + 60; return b;
}
 
int main()
{  Time now;
   cout << "Now it is " << now << "\n";
   Time later = now + 1000;
   cout << "A thousand seconds later it is " << later << "\n";
   Time now2;
   if (now == now2)
      cout << "It still is " << now2 << "\n";
   if (now != now2)
      cout << "It is already " << now2 << "\n";
   cout << "Another " << later - now2 << " seconds until "
        << later << "\n";
   now = Time();
   cout << "Now it is " << now << "\n";
   cout << "One second later " << (++now) << "\n";
   cout << "The same time " << (now++);
   cout << " and 60 seconds later " << now << "\n";
   return 0;
}

Now it is 11:42:51
A thousand seconds later it is 11:59:31
It still is 11:42:51
Another 1000 seconds until 11:59:31
Now it is 11:42:51
One second later 11:42:52
The same time 11:42:52 and 60 seconds later 11:43:52

**Предефиниране на операции като член-функции на клас. 
// overload1.cpp
#include <iostream>
#include <iomanip>
using namespace std;
#include "ccc_time.h"
class Time_new : public Time {
public:
int operator-(Time_new b) const;
Time_new operator+(int sec) const;
bool operator==(Time_new b) const;
bool operator!=(Time_new b) const;
Time_new operator++(); // prefix
Time_new operator++(int dummy); // postfix
friend ostream& operator<<(ostream& out, Time_new a);
};

/**
Compute the number of seconds between two points in time.
@param b another point in time
@return the number of seconds that a is away from b
*/
int Time_new::operator-(Time_new b) const
{ return this->seconds_from(b);
}

/**
Compute a point in time that is some number of seconds away.
@param sec the seconds to add
@return a point in time that is sec seconds away from a
*/
Time_new Time_new::operator+(int sec) const
{ Time_new r = *this;
r.add_seconds(sec);
return r;
}

/**
Compare two points in time
@param b another point in time
@return true if they are the same
*/
bool Time_new::operator==(Time_new b) const
{ return this->seconds_from(b) == 0;
}

/**
Compare two points in time.
@param b another point in time
@return true if they are the different
*/
bool Time_new::operator!=(Time_new b) const
{ return !(*this == b);
}

/**
Prefix increment by 1 second.
@return the new value
*/
Time_new Time_new::operator++() // prefix
{ *this = *this + 1;
return *this;
}

/**
Postfix increment by 1 second.
@return the old value
*/

Time_new Time_new::operator++(int dummy) // postfix
{ Time_new t = *this;
*this = *this + 1;
return t;
}


/**
Print a Time object
@param out an output stream
@param a a point in time
@return out
*/
ostream& operator<<(ostream& out, Time_new a)
{
out << a.get_hours() << ":"
<< setw(2) << setfill('0')
<< a.get_minutes() << ":"
<< setw(2) << a.get_seconds() << setfill(' ');
return out;
}


int main()
{
Time_new now;
cout << "now: " << now << endl;
Time_new later = now + 1000;
cout << "later: " << later << endl;
Time_new now2;
if (now == now2)
cout << "now == now2: " << now2 << endl;
if (now != now2)
cout << "now != now2 " << now2 << endl;
cout << "now++: " << now++
<< " ++now2: " << ++now2 << endl;
cout << "now: " << now << " now2: " << now2 << endl;
cout << "later - now2: " << later - now2 << endl;
return 0;
}

Сега можем да заменим функциите, които заместваха операциите от STL-реализацията на свързан списък (list2.cpp) с предефинирани операции:

bool operator!=(Iterator a, Iterator b); /* външна за класа функция */
bool Iterator::operator!=(Iterator b);   /* член-функция */

string Iterator::operator*() const
{  assert(position != NULL);
   return position->data;  }

void Iterator::operator++(int dummy)
{  assert(position != NULL);
   position = position->next; }

void Iterator::operator--(int dummy)
{  if (position == NULL)  position = last;
   else                   position = position->previous;
   assert(position != NULL);   }

bool Iterator::operator!=(Iterator b) const
{  return position != b.position;   }

bool Iterator::operator==(Iterator b) const
{  return position == b.position;   


Управление на паметта

Ще разгледаме примера department.cpp.

class Department {
...
private: string name;
Employee* receptionist;
};

Department::Department(string n, Employee e)
{
name = n; receptionist = new Employee(e.get_name(), e.get_salary());
}

/* second constructor */
Department::Department(string n)
{
name = n;
receptionist = NULL;
}

** Деструктор

Деструкторът е специална член-функция, която се извиква автоматично когато обектът излезе от обхват.
Деструктурът на класа Department трябва да унищожи създадения от конструктора обект:

Department::~Department()
{ delete receptionist;
}

** Предефиниране на операция за присвояване

Нека имаме следните дефиниции:

Department qc("Quality Control", Employee("Tester, Tina", 50000));
Department dept("Shipping", Employee("Hacker, Harry", 35000));

и след това приложим операция присвояване (почленно копиране)

dept = qc;

Това присвоявано води до загуба на памет (memory leak)! Когато един от обектите излезе от обхват, получаваме висящ указател!



Затова трябва да предефинираме операция присвояване за обекти от класа Employee

Department& Department::operator=(const Department& b)
{ if (this != &b)
{ name = b.name;
delete receptionist;
if (b.receptionist == NULL) receptionist == NULL;
else
receptionist = new Employee(b.receptionist->get_name(),
b.receptionist->get_salary());
}
return *this;
}

Корираме данните, които не са указатели (в случая name) от обект b в *this, изтриваме указателя receptionist от *this и създаваме нов обект от тип Employee за *this, копие на обекта *receptionist от b

Функцията за предефиниране трябва да е член-функция (защо)?

** Конструктор за копиране

Предназначението на операция присвояване е да промени съшествуващ обект, като го направи същия, както друг обект и не е подходящ за конструиране на нов обект. Напр. в оператора

Department dept = qc; // not assignment operator!

не може да се използва (и не е) операция присвояване. Правилното конструиране на обект, копие на друг обект е:

Department dept(qc);

т.е. като се извика конструктор за копиране. За всеки клас в езика С++ има такъв конструктор, но той действа с почленно копиране и възникава същия проблем, както при операция присвояване. Решението е да се напише конструктор за копиране:

Department::Department(const Department& b)
{ name = b.name;
if (b.receptionist == NULL) receptionist = NULL;
else
receptionist = new Employee(b.receptionist->get_name(),
b.receptionist->get_salary());
}

Конструктор за копиране се вика и когато се предава параметър-стойност на функция, напр.

void print(Department d)
{ ... }

Department dep("Administration")
...
print(dep);
....

Пример:

// department.cpp
001: #include <string> 002: #include <iostream> 004: using namespace std; 005: 006: #include "ccc_empl.h" 008: /** 009: A department in an organization. 010: */ 011: class Department { 013: public: 014: Department(string n); 015: Department(string n, Employee e); 016: ~Department(); 017: Department& operator=(const Department& b); 018: Department(const Department& b); 019: void print() const; 020: private: 021: string name; 022: Employee* receptionist; 023: }; 024: 025: /** 026: Constructs a department with a given name and no receptionist. 027: @param n the department name 028: */ 029: Department::Department(string n) 030: { name = n; 032: receptionist = NULL; 034: cout << "Constructor: "; 035: print(); 036: } 037: 038: /** 039: Constructs a department with a given name and receptionist. 040: @param n the department name 041: @param e the receptionist 042: */ 043: Department::Department(string n, Employee e) 044: { name = n; 046: receptionist = new Employee(e.get_name(), e.get_salary()); 048: cout << "Constructor: "; 049: print(); 050: } 051: 052: /** 053: Deletes the Employee object that this Department 054: object manages. 055: */ 056: Department::~Department() 057: { cout << "Destructor: "; 059: print(); 061: delete receptionist;
062: } 063: 064: /** 065: Constructs a Department object as a copy of another 066: Department object. 067: @param b the object to copy 068: */ 069: Department::Department(const Department& b) 070: { cout << "Copy constructor: "; 072: b.print(); 073: 074: name = b.name; 075: if (b.receptionist == NULL) receptionist = NULL; 077: else 078: receptionist = new Employee(b.receptionist->get_name(), 079: b.receptionist->get_salary()); 080: } 081: 082: /** 083: Sets this Department object to a copy of another 084: Department object. 085: @param b the object to copy 086: */ 087: Department& Department::operator=(const Department& b) 088: { cout << "Assignment: "; 090: print(); 091: cout << "= "; 092: b.print(); 093: 094: if (this != & b) 095: { name = b.name; 097: delete receptionist; 098: if (b.receptionist == NULL) receptionist = NULL; 100: else 101: receptionist = new Employee(b.receptionist->get_name(), 102: b.receptionist->get_salary()); 103: } 104: return *this; 105: } 106: 107: /** 108: Prints a description of this department. 109: */ 110: void Department::print() const 111: { cout << "[name=" << name << ",receptionist="; 113: if (receptionist == NULL) cout << "NULL"; 115: else cout << receptionist->get_name(); 117: cout << "]\n"; 118: } 119: 120: int main() 121: { Department shipping("Shipping"); 123: Department qc("Quality Control",
124: Employee("Tester, Tina", 50000)); 125: Department dept(qc);
126: dept = shipping; 127: return 0; 128: }
Когато използваме указатели (и динамична памет) в данните на клас, винаги трябва да дефинираме "големите три":
Пример с класа List (да си припомним list2.cpp):

// list0.cpp
#include <string>
#include <iostream>
#include <cassert>
using namespace std;

/* forward declarations */
class List;
class Iterator;

/** A class to hold the nodes of the linked list.
*/
class Node {
public:
  
/** Constructs a node for a given data value.
       @param s the data to store in this node
   */
   Node(string s);
private:
   string data;
   Node* previous;
   Node* next;
friend class List;
friend class Iterator;
};
  
/** An iterator denotes a position in the list or
    past the end of the list.
*/
class Iterator {
public:
  
/** Constructs an iterator that is not attached to any list.
   */
   Iterator();
  
/** Looks up the value at a position.
       @return the value of the Node to which the iterator
       points
   */
   string operator*() const;
  
/** Advances the iterator to the next position.
   */
   void operator++(int dummy);
  
/** Moves the iterator to the previous position.
   */
   void operator--(int dummy);
  
/** Compares two iterators.
       @param b the iterator to compare with this iterator
       @return true if this iterator and b are equal
   */
   bool operator==(Iterator b) const;
  
/** Compares two iterators.
       @param b the iterator to compare with this iterator
       @return true if this iterator and b are not equal
   */
   bool operator!=(Iterator b) const;
private:
   Node* position;
   Node* last;
friend class List;
};

/** A linked list of values of a given type.
    @param T the type of the list values
*/
class List {
public:
  
/** Constructs an empty list.
   */
   List();
  
/** Constructs a list as a copy of another list.
       @param b the list to copy
   */
   List(const List& b);
  
/** Deletes all nodes of this list.
   */
   ~List();
  
/** Assigns another list to this list.
       @param b the list to assign
       @return a reference to this list
   */
   List& operator=(const List& b);
  
/** Appends an element to the list.
       @param s the value to append
   */
   void push_back(string s);
  
/** Inserts an element into the list.
       @param iter the position before which to insert
       @param s the value to append
   */
   void insert(Iterator iter, string s);
  
/** Removes an element from the list.
       @param i the position to remove
       @return an iterator pointing to the element after the
       erased element
   */
   Iterator erase(Iterator i);
  
/** Gets the beginning position of the list.
       @return an iterator pointing to the beginning of the list
   */
   Iterator begin() const;
  
/** Gets the past-the-end position of the list.
       @return an iterator pointing past the end of the list
   */
   Iterator end() const;

private:
  
/** Copies another list to this list.
       @param b the list to copy
   */
   void copy(const List& b);
  
/** Deletes all nodes of this list.
   */
   void free();
   Node* first;
   Node* last;
};

List::List()
{  first = NULL;
   last = NULL;
}

List::~List()
{  free();
}

List::List(const List& b)
{  first = NULL;
   last = NULL;
   copy(b);
}

List& List::operator=(const List& b)
{  if (this != &b)
   {  free();
      copy(b);
   }
   return *this;
}

void List::push_back(string s)
{  Node* newnode = new Node(s);
   if (last == NULL) /* list is empty */
   {  first = newnode;
      last = newnode;
   }
   else
   {  newnode->previous = last;
      last->next = newnode;
      last = newnode;
   }
}

void List::insert(Iterator iter, string s)
{  if (iter.position == NULL)
   {  push_back(s);
      return;
   }

   Node* after = iter.position;
   Node* before = after->previous;
   Node* newnode = new Node(s);
   newnode->previous = before;
   newnode->next = after;
   after->previous = newnode;
   if (before == NULL) /* insert at beginning */
      first = newnode;
   else
      before->next = newnode;
}

Iterator List::erase(Iterator i)
{  Iterator iter = i;
   assert(iter.position != NULL);
   Node* remove = iter.position;
   Node* before = remove->previous;
   Node* after = remove->next;
  
   if (remove == first) first = after;
   else          before->next = after;
   if (remove == last) last = before;
   else     after->previous = before;
  
   iter.position = after;
   delete remove;
   return iter;
}

Iterator List::begin() const
{  Iterator iter;
   iter.position = first;
   iter.last = last;
   return iter;
}

Iterator List::end() const
{  Iterator iter;
   iter.position = NULL;
   iter.last = last;
   return iter;
}

Iterator::Iterator()
{  position = NULL;
   last = NULL;
}

string Iterator::operator*() const
{  assert(position != NULL);
   return position->data;
}

void Iterator::operator++(int dummy)
{  assert(position != NULL);
   position = position->next;
}

void Iterator::operator--(int dummy)
{  if (position == NULL) position = last;
   else                  position = position->previous;
   assert(position != NULL);
}

bool Iterator::operator==(Iterator b) const
{  return position == b.position;
}

bool Iterator::operator!=(Iterator b) const
{  return position != b.position;
}

Node::Node(string s)
{  data = s;
   previous = NULL;
   next = NULL;
}

void List::copy(const List& b)
{  for (Iterator p = b.begin(); p != b.end(); p++)
      push_back(*p);
}

void List::free()
{  while (begin() != end()) erase(begin());
}

int main()
{  List staff;

   staff.push_back("Cracker, Carl");
   staff.push_back("Hacker, Harry");
   staff.push_back("Lam, Larry");
   staff.push_back("Sandman, Susan");

   /* add a value in fourth place */
   Iterator pos;
   pos = staff.begin();
   pos++;
   pos++;
   pos++;

   staff.insert(pos, "Reindeer, Rudolf");


   /* remove the value in second place */
   pos = staff.begin();
   pos++;

   staff.erase(pos);

  
/* print all values */
   for (pos = staff.begin(); pos != staff.end(); pos++)
      cout << *pos << "\n";

   return 0;
}