Загрузка...

воскресенье, 25 мая 2008 г.

Грабли C++. Наследование

Как и обещал, публикую продолжение темы граблей. Предыдущая статья - "Грабли C++. Конструкторы".

Пример:

class Employee
{
public:
 Employee(string name, string dept);
 virtual void print() const;
 string dept() const;
private:
 string _name;
 string _dept;
};

class Manager : public Employee
{
public:
 Manager(string name, string dept);
 virtual void print() const;
private:
// ...
};

void Employee::print() const
{  
 cout << _name << endl;
}  

void Manager::print() const
{  
 // вызовы функций базового класса
 cout << dept() << endl;
 print(); 
}  
Грабли:
void Manager::print() const
{  
// вызовы функций базового класса
 cout << dept() << endl;
 print();
}  
Функция dept() принадлежит классу Employee и ее вызов происходит в "штатном режиме" в то время, как виртуальная функция print() переопределена в производном классе Manager. Поэтому ее ожидаемого вызова не происходит. В довесок, в этом примере происходит бесконечная рекурсия. Мораль: чтобы избежать такой ситуации, необходимо указывать пространтсво имен, из которой необходимо вызвать виртуальную функцию:
void Manager::print() const
{  
 cout << dept() << endl;
 Employee::print();
} 
Пример:
class Employee;
void Manager::print() const
{  
 cout << dept() << endl;
 Employee:print(); 
}  
Грабли:
Employee:print();
Понятно, что должно быть так:
Employee::print();
Но почему этот пример удачно компилируется? Потому что Employee явлется меткой. Мораль: стоит внимательно смотреть за оператором расширения пространства имен ::. Пример:
class Employee
{
public:
 void raise_salary(double by_percent);
 // ...
};

class Manager : public Employee
{
public:
 // ...
};

void make_them_happy(Employee* e, int ne)
{  
 for (int i = 0; i < ne; i++)
  e[i].raise_salary(0.10);
}     

int main()
{  
 Employee e[20];
 Manager m[5];
 m[0] = Manager("Иван Иванов", "Продажи");
 // ...
 make_them_happy(e, 20);
 make_them_happy(m + 1, 4); // пропустим Иванова
 return 0;
}
Слабо определить, где грабли? Грабли:
void make_them_happy(Employee* e, int ne);
Manager m[5];
make_them_happy(m + 1, 4);
Что происходит в этом коде?
m + 1 - это тип Manager*. Через наследование, указатель на Manager становится указателем на Employee. "И что тут странного?" - спросите вы.

Все дело в том, что при выполнении операции e[i] происходит смещение внутри массива на значение, равное i*sizeof(Employee).

Мораль: не стоит злоупотреблять использованием указателей. Есть две трактовки записи Employee* e:
1. е указывает либо на объект класса Manager, либо на объект одно из его производных классов, например Employee;
2. e указывает либо на объект класса Manager, либо на их набор (массив).

Эти две трактовки несовместимы между собой. Путаница в них приводит к ошибкам времени выполнения. (В результате выполнения этого кода, происходит "затирание" полезных участков памяти.)

Данную тему можно продолжать бесконечно. Оригинальную статью по описанным выше граблям можно найти здесь. В ней вы найдете еще больше примеров.

2 коммент.:

Анонимный комментирует...

по моему во втором примере
-------
Грабли:
тут ----> Employee:print();
Понятно, что должно быть так:
и тут --> Employee:print();
-------
написано одно и тоже.
наверно во втором случае должно быть :: (два двоеточия)

Сергей комментирует...

да, действительно)
это была проверка на бдительность ;)

Отправить комментарий | Feed



 
^

Powered by BloggerCreative Commons License