|
Программирование >> Программирование на языке c++
теле этой функции. Любой автоматический объект конструируется тогда, когда встречается его объявление, и разрушается, когда блок, в котором он описан, прекращает существовать. После завершения функция f прекращает существовать. В результате вызывается деструктор объекта о. Таким образом, выполняются следующие действия: ♦ описание X о; задает конструирование нового объекта о. Конструктор объекта о вьщеляет (динамически) память с помощью оператора new; ♦ вызывается функция f; ♦ значение объекта о копируется из функции main в стек функции f; ♦ копия объекта о содержит указатель на ту же динамическую память (указатели на динамическую память в объекте-оригинале и в объекте-копии имеют одни и те же значения); ♦ функция f завершается; ♦ вызывается деструктор для копии объекта о, который разрушает динамически выделенную память; ♦ теперь указатель в оригинальном объекте адресует несуществующую (удаленную) память. Если объявить функцию f в виде f(X&), то ошибка будет устранена. При необходимости можно оставить и предьщу-щее объявление. В этом случае надо устранить ошибку в самом классе X. Когда объект о копируется из функции main в функцию f, то вызывается соответствующий конструктор для копирования. Поскольку в нашем классе такого конструктора нет, то вызывается конструктор, заданный по умолчанию. Этот конструктор строит точную копию всех данных объекта о, что, в конечном счете, и приводит к ошибке. Если в классе X задать явно конструктор для копирования, например: X::X(const Х& о) { i = newint[2]; i[0] = o.i[0]; i[1] = o.i[1]; } TO ошибка будет устранена. Таким образом, если в конструкторе некоторого класса X осуществляется динамическое выделение памяти, такой класс должен иметь соответствующий конструктор для копирования, а также деструктор (для осво- бождения памяти). Аналогичное правило распространяется и на оператор присваивания (=), поскольку оператор присваивания, заданный по умолчанию, порождает те же проблемы. Дополнительную информацию по рассмотренным вопросам можно найти в работе [16]. 2.2. Виртуальные функции. Если в некотором классе задана хотя бы одна виртуальная функция, то все объекты этого класса содержат указатель на связанную с их классом виртуальную таблицу. Эта виртуальная таблица содержит адреса (указатели на первые инструкции) действительных функций, которые будут вызваны. Пусть заданы базовый В и производный D классы (B<-D). В базовом классе В задана виртуальная функция yi. В производном классе D содержится улучшенная версия этой функции vf. Рассмотрим некоторую глобальную функцию f(B *pb) и объявления: В Ь; D d;. Предположим, что выполняются следующие действия: ♦ в функцию f передается указатель на объект. Для вызова f(&b) это будет указатель на объект базового класса, а для вызова f(&d) - указатель на объект производного класса; ♦ в теле функции f вызывается виртуальная функция vf (pb->vf(...);). В результате выполняются следующие дей-, ствия. В объекте с заданным адресом находится адрес виртуальной таблицы. Если pb - адрес базового объекта, то находится адрес виртульной таблицы базового объекта. Если pb - адрес производного объекта, то находится адрес виртуальной таблицы производного объекта. Имя функции vf задает смещение в соответствующей виртуальной таблице для нахождения указателя на функцию vf (адреса первой инструкции в теле функции vf). После выбора первой инструкции функции vf она выполняется как обычно. Более детальное пояснение описанных действий с рисунками и программами на языке ассемблера дано в [16]. Если класс имеет хотя бы одну виртуальную функцию, то он должен иметь виртуальный деструктор [1]. Это позволяет устранить проблемы, связанные с разрушением объектов производного класса. Рассмотрим пример: void main(void) { В *pb = new D; delete pb; } Здесь pb - указатель на объект базового класса В, но в действительности рЪ указывает на объект производного класса D. Если деструктор в классе В не виртуальный, то мы будем иметь такую последовательность вызовов конструкторов и деструкторов: 1) конструктор В; 2) конструктор D; 3) деструктор В. В результате один деструктор (для объекта D) не вызван. Объявление деструктора виртульным в классе В устраняет эту проблему. 2.3. Виртуальные базовые классы. Рассмотрим пример: void main(void) { В b; D1 d1; B<-D1 D2 d2; B<-D2 D1D2 d1d2; } (D1,D2)<-D1D2 Для невиртульного базового класса В каждый объект класса D1D2 имеет два подобъекта класса В, т.е. dld2 имеет такую структуру в памяти компьютера, что в ней область данных объекта b встречается два раза. Если это порождает проблемы, то класс В может быть объявлен виртуальным. В этом случае каждый объект класса D1D2 имеет только один подобъект класса В, т.е. dld2 имеет такую структуру в памяти компьютера, что в ней область данных объекта b встречается один раз, и доступ к этой области из объекта dld2, а также из подобъектов dl, d2 объекта dld2 осуществляется через ее адрес (через адрес подобъекта b в структуре dld2). Более детальные пояснения по этому вопросу с графическими иллюстрациями даны в работе [16]. 2.4. Объекты, действующие как указатели (smart pointers). Для того чтобы создать такие объекты, необходимо переопределить оператор ->. Рассмотрим пример: template <class Т> class Р { Т *р; public: Р(Т *тр = NULL) : p(mp) { } ~Р(){ delete р; } Т* operator->() const { return р; } }; Теперь, чтобы получить доступ к элементам класса Т, объекты класса Р можно использовать так же, как указатели. Рассмотрим следующую функцию: void F(int I) { Р<Х> px = new X(l); px->fX(); }
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |