С этими ответами вы входите в гипотезы, поэтому для ясности я постараюсь дать более простое и приземленное объяснение.
Основных отношений объектно-ориентированного проектирования два: IS-A и HAS-A. Я не придумывал их. Так их называют.
IS-A указывает, что конкретный объект идентифицируется как принадлежащий к классу, который находится над ним в иерархии классов. Объект "банан" является объектом-фруктом, если он является подклассом класса фруктов. Это означает, что везде, где можно использовать фруктовый сорт, можно использовать банан. Однако это не рефлексивно. Вы не можете заменить базовый класс конкретным классом, если этот конкретный класс требуется.
Has-a указывает, что объект является частью составного класса и что существует отношение владения. В C ++ это означает, что это объект-член, и поэтому класс-владелец должен избавиться от него или передать право владения перед самоуничтожением.
Эти две концепции легче реализовать в языках с одиночным наследованием, чем в модели с множественным наследованием, такой как C ++, но правила по сути те же. Сложность возникает, когда идентичность класса неоднозначна, например, передача указателя класса Banana в функцию, которая принимает указатель класса Fruit.
Виртуальные функции - это, во-первых, время выполнения. Это часть полиморфизма, поскольку он используется для решения, какую функцию запускать во время ее вызова в запущенной программе.
Ключевое слово virtual - это директива компилятора для привязки функций в определенном порядке, если есть двусмысленность относительно идентичности класса. Виртуальные функции всегда находятся в родительских классах (насколько мне известно) и указывают компилятору, что привязка функций-членов к их именам должна происходить сначала с функцией подкласса, а затем с функцией родительского класса.
Класс Fruit может иметь виртуальную функцию color (), которая по умолчанию возвращает «NONE». Функция color () класса Banana возвращает «ЖЕЛТЫЙ» или «КОРИЧНЕВЫЙ».
Но если функция, принимающая указатель Fruit, вызывает color () для отправленного ей класса Banana - какая функция color () будет вызвана? Функция обычно вызывает Fruit :: color () для объекта Fruit.
В 99% случаев это было не то, что было задумано. Но если Fruit :: color () объявлен виртуальным, то для объекта будет вызываться Banana: color (), потому что правильная функция color () будет привязана к указателю Fruit во время вызова. Среда выполнения проверит, на какой объект указывает указатель, поскольку он был помечен как виртуальный в определении класса Fruit.
Это отличается от переопределения функции в подклассе. В этом случае указатель Fruit вызовет Fruit :: color (), если все, что он знает, - это то, что это Я-указатель на Fruit.
Итак, теперь возникает идея «чистой виртуальной функции». Это довольно неудачная фраза, потому что чистота тут ни при чем. Это означает, что предполагается, что метод базового класса никогда не будет вызываться. Действительно, чистую виртуальную функцию вызвать нельзя. Однако это еще предстоит определить. Должна существовать сигнатура функции. Многие программисты создают пустую реализацию {} для полноты, но компилятор сгенерирует ее внутри, если нет. В том случае, когда функция вызывается, даже если указатель находится на Fruit, будет вызываться Banana :: color (), поскольку это единственная реализация color (), которая существует.
Теперь последний кусок головоломки: конструкторы и деструкторы.
Чистые виртуальные конструкторы полностью запрещены. Это только что вышло.
Но чистые виртуальные деструкторы работают в том случае, если вы хотите запретить создание экземпляра базового класса. Только подклассы могут быть созданы, если деструктор базового класса является чисто виртуальным. соглашение заключается в том, чтобы присвоить ему значение 0.
virtual ~Fruit() = 0; // pure virtual
Fruit::~Fruit(){} // destructor implementation
В этом случае вам нужно создать реализацию. Компилятор знает, что вы делаете, и следит за тем, чтобы вы все делали правильно, или сильно жалуется, что не может связать все функции, которые ему нужны для компиляции. Ошибки могут сбивать с толку, если вы не на правильном пути при моделировании иерархии классов.
Таким образом, в этом случае вам запрещено создавать экземпляры Fruit, но разрешено создавать экземпляры Banana.
Вызов удаления указателя Fruit, указывающего на экземпляр Banana, сначала вызывает Banana :: ~ Banana (), а затем всегда вызывает Fuit :: ~ Fruit (). Потому что, несмотря ни на что, когда вы вызываете деструктор подкласса, деструктор базового класса должен следовать.
Это плохая модель? Да, это сложнее на этапе проектирования, но он может гарантировать, что правильное связывание выполняется во время выполнения и что функция подкласса выполняется там, где есть двусмысленность относительно того, к какому подклассу осуществляется доступ.
Если вы пишете C ++ так, чтобы передавать только точные указатели классов без общих или неоднозначных указателей, то виртуальные функции на самом деле не нужны. Но если вам требуется гибкость типов во время выполнения (как в Apple Banana Orange ==> Fruit), функции становятся проще и универсальнее с меньшим количеством избыточного кода. Вам больше не нужно писать функцию для каждого типа фруктов, и вы знаете, что каждый фрукт будет реагировать на color () своей собственной правильной функцией.
Я надеюсь, что это длинное объяснение укрепляет концепцию, а не сбивает с толку. Есть много хороших примеров, на которые стоит взглянуть, и посмотреть на них достаточно, и на самом деле запустить их, возиться с ними, и вы получите это.
person
Chris Reid
schedule
11.04.2017