Как и обещал, публикую продолжение темы граблей. Предыдущая статья - "Грабли 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();
-------
написано одно и тоже.
наверно во втором случае должно быть :: (два двоеточия)
да, действительно)
это была проверка на бдительность ;)