Прежде чем читать дальше, попробуйте выяснить, сколько ошибок вы можете найти в функции f()
poor() может привести к краху программы?На первый взгляд данный вызов выглядит отлично, но это именно тот вид кода, который приносит программистам бессонные ночи отладки и вызывает кошмары у инженеров по качеству.
1. Передается элемент неправильного типа (например, poor(&s0[0],s0.size()
s0 может быть пустым, а в этом случае выражение &s0[0] является неверным.2. Используется “магическая константа” (в данном случае правильная): poor(s1,10)
3. Используется “магическая константа” (в данном случае неправильная): poor(s2,20)
4. Первый вызов poor(p1,1)
5. Передача нулевого указателя при втором вызове: poor(p1,1)
6. Вызов poor(q,max)
q на массив, содержащий хотя бы max элементов, мы должны найти определения указателя q и переменной max и их значения при данном вызове.В каждом из перечисленных вариантов ошибки были простыми. Мы не столкнулись с какими-либо скрытыми ошибками, связанными с алгоритмами и структурами данных. Проблема заключается в интерфейсе функции poor()
p1 и s0. Тем не менее мнемонические, но неправильные имена могут породить еще более сложные проблемы.Теоретически компилятор может выявить некоторые из этих ошибок (например, второй вызов poor(p1,1)
p1==0), но на практике мы избежали катастрофы в данном конкретном случае только потому, что компилятор предотвратил создание объектов абстрактного класса Shape. Однако эта ошибка никак не связана с плохим интерфейсом функции poor(), поэтому мы не должны расслабляться. В дальнейшем будем использовать вариант класса Shape, который не является абстрактным, так что избежать проблем с интерфейсом нам не удастся.Как мы пришли к выводу, что вызов poor(&s0[0],s0.size())
&s0[0] относится к первому элементу массива объектов класса Circle; он является значением указателя Circle*. Мы ожидаем аргумент типа Shape* и передаем указатель на объект класса, производного от класса Shape (в данном случае Circle*). Это вполне допустимо: нам необходимо такое преобразование, чтобы можно было обеспечить объектно-ориентированное программирование и доступ к объектам разных типов с помощью общего интерфейса (в данном случае с помощью класса Shape) (см. раздел 14.2). Однако функция poor() не просто использует переменную Shape* как указатель; она использует ее как массив, индексируя ее элементы.for (int i = 0; i
Иначе говоря, она ищет элементы, начиная с ячеек &p[0]
&p[1], &p[2] и т.д.В терминах адресов ячеек памяти эти указатели находятся на расстоянии sizeof(Shape)
poor(), значение sizeof(Circle) больше, чем sizeof(Shape), поэтому схему распределения памяти можно проиллюстрировать так.Другими словами, функция poor()
draw() с указателем, ссылающимся в середину объекта класса Circle! Это скорее всего приведет к немедленной катастрофе (краху)! poor(s1,10)
Polygon сразу не привело к проблемам, которые мы обнаружили при использовании объектов класса Circle, заключается в том, что класс Polygon не добавляет члены класса к базовому классу Shape (в отличие от класса Circle; см. разделы 13.8 и 13.12), т.е. выполняется условие sizeof(Shape)==sizeof(Polygon) и — говоря более общо — класс Polygon имеет ту же самую схему распределения памяти, что и класс Shape. Иначе говоря, нам просто повезло, так как небольшое изменение определения класса Polygon приведет программу к краху. Итак, вызов poor(s1,10) работает, но его ошибка похожа на мину замедленного действия. Этот код категорически нельзя назвать качественным.То, с чем мы столкнулись, является основанием для формулировки универсального правила, согласно которому из утверждения “класс D
B” не следует, что “класс Container — это разновидность класса Container” (см. раздел 19.3.3). Рассмотрим пример.