|
Программирование >> Аргументация конструирования
ф(/шсцил не ллйлйля Даже если вы считаете, что каждая функция вызывается с использованием позднего связывания, это отнюдь не означает, что так и есть на самом деле. C++ на этапе компилирования никак не указывает, какое связывание было использовано - ранее или позднее. Для осуществления вызова с поздним связыванием нужно следить за тем, чтобы все необходимые функции-чдены бьши объявлены идентично, включая возвращаемый тип. Если объявлена в подклассе с другими аргументами, она не будет переопределена как полиморфная, независимо от того, объявлена она виртуальной или нет. Например, измените приведенную выше функцию так, чтобы ее аргументы не совпадали с аргументами функции базового класса, и вновь выполните программу. #include <iostreani. h> class Base public: virtual voi (int x) cout Мы в классе Base, int x = x Xn ; Subclass Mic Xie public: cout Mi в классе float x = x \n ; voi (Bases b) int i = l, b.fn(i); Здесв не используется позднее связывание float f = 2.OF; b.fn(f); И здесв тоже не исполвзуется in, (int argcs, char* pArgs[]) { Ease be; Subclass sc; cout << Вызываем функцию test(bc)\n ; test; (be); cout Вызываем функцию test(sc)\n ; test(sc) ; return 0; Единственное отличие между этой и предыдущей программой в том, что функция fn () в классе Base объявлена как fn(int), тогда как в версии класса Subclass она объявлена как fn (float). Никакой ошибки это не вызовет, поскольку программа полностью корректна. Однако результаты не показывают никаких признаков полиморфизма: Вызываем функцию test(bc) Мы в классе Base, int х = 2 Мы в классе Вазе, int х = 2 Вызываем функцию test{sc) Мы в классе Base, int х = 1 Мы в классе 3ase, int х = 2 Поскольку первый вызов передает значение типа int, не удивительно, что компилятор вызывает fn(int) как с Ьс, так и с sc. Несколько неожиданно то, что во втором вызове float конвертируется в int и при втором обращении к функции test (} вызывается та же функция Base: : fn(). Это происходит потому, что объект ь, который передается функции test (), является объектом класса Base. Без полиморфизма вызовЬ.£п() Btest{) обращается к Base: : fn(int). Если аргументы не полностью совпадают, позднее связывание не используется. В правиле об идентичности объявления есть только одно исключение, которое состоит в том, что если функция-член базового класса возвращает указатель или ссылку на объект базового класса, то переопределяемая функция-член может возвращать указатель или ссылку на объект подкласса. Другими словами, приведенная ниже программа допустима. class Base public: возвращаем копию текущего объекта Base* makeACopyO { ...делает все, что нужно для создания копии class Subclass : public Base public: возвращаем копию текущего объекта Subclass* makeACopyO ...делает все, что нужно для создания копии void fn (BaseClasst. be) BaseClass* pCopy = be.makeACopy(); функция продолжается. . . С практической точки зрения все естественно: функция копирования гаакеСоруО должна возвращать указатель на объект типа Subclass, даже если она переопределяет Base:imakeCopy(). BufuucubHJbte осб&шюс При использовании виртуальных функций следует не забывать о некоторых вещах. Во-первых, статические функции-члены не могут быть объявлены виртуальными. Поскольку статические функции-члены не вызываются с объектом, никакого объекта этапа выполнения не может быть, а значит, нет и его типа. Во-вторых, при указании имени класса в вызове функция будет компилироваться с использованием раннего связывания независимо от того, объявлена она виртуальной или нет. Например, приведенный ниже вызов обращается к Base::fn(], поскольку так указал программист, независимо от того, объявлена f n () виртуальной или нет. void test (Bases b) b.base: : fn () ,- Этот вызов т позднего связывания Кроме того, виртуальная функция не может быть встроенной. Чтобы подставить функцию на место ее вызова, компилятор должен знать ее на этапе компиляции. Таким образом, независимо от способа описания виртуальные функции-члены рассматриваются как не встроенные. И наконец, конструкторы не могут быть виртуальными, поскольку во время работы конструктора не существует завершенного объекта какого-либо определенного типа. В момент вызова конструктора память, выделенная для объекта, является просто аморфной массой. И только после окончания работы конструктора объект становится экземпляром класса в полном смысле этого слова. В отличие от конструктора, деструктор может быть объявлен виртуальным. Более того, если он не объявлен виртуальным, вы рискуете столкнуться с неправильной ликвидацией объекта, как, например, в следующей ситуации: class Base public: -Base() ; class Subclass : public Base ( public: ); -Subclass 0 , voi bject(Base* pHeapObject) delete pHeapObject; Здесь вызывается -BaseO независимо от типа указателя pHeapObject Если указатель, передаваемый функции finishWithObject (), на самом деде указывает на объект subclass, деструктор Subclass все равно вызван не будет: поскольку он не был объявлен виртуальным, используется раннее связывание. Однако, если объявить деструктор виртуальным, проблема будет решена. А если вы не хотите объявлять деструктор виртуальным? Тому может быть только одна причина: виртуальные функции несколько увеличивают размер объекта. Когда программист определяет первую виртуальную функцию в классе, C++ прибавляет
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |