Здавалка
Главная | Обратная связь

Возврат объекта из функции.



Похожая проблема возникает и при использовании объекта в качестве возвращаемого значения.

Для того чтобы функция могла возвращать объект, нужно: во-первых, объявить функцию так, чтобы ее возвращаемое значение имело тип класса, во-вторых, возвращать объект с помощью обычного оператора return. Однако если возвращаемый объект содержит деструктор, то в этом случае возникают проблемы, связанные с "неожиданным" разрушением объекта.

# include <iostream>using namespace std; class ClassName{public: ClassName () { cout << "ClassName!!!\n"; } ~ClassName () { cout << "~ClassName!!!\n"; }}; ClassName f(){ ClassName obj; cout << "Function f\n"; return obj;} void main(){ ClassName c1; f();} Результат работы программы: ClassName!!!ClassName!!!Function f~ClassName!!!~ClassName!!!~ClassName!!!

Конструктор вызывается два раза: для с1 и obj. Однако деструкторов здесь три. Как же так? Ясно, что один деструктор разрушает с1, еще один - obj. "Лишний" вызов деструктора (второй по счету) вызывается для так называемого временного объекта, который является копией возвращаемого объекта. Формируется эта копия, когда функция возвращает объект. После того, как функция возвратила свое значение, выполняется деструктор временного объекта. Естественно, что если деструктор, например, высвобождает динамически выделенную память, то разрушение временного объекта приведет к разрушению возвращаемого объекта.

Инициализация одного объекта другим при создании.

В программировании есть еще один случай побитового копирования - инициализация одного объекта другим при создании:

# include <iostream>using namespace std; class ClassName{public: ClassName () { cout << "ClassName!!!\n"; } ~ClassName () { cout << "~ClassName!!!\n"; }}; void main(){ ClassName c1; // Вот он!!! Момент побитового копирования. ClassName c2=c1;} Результат работы программы: ClassName!!!~ClassName!!!~ClassName!!!

Конструктор вызывается один раз: для с1. Для с2 конструктор не срабатывает. Однако деструктор срабатывает для обоих объектов. А, поскольку, с2 является точной копией с1, деструктор, высвобождающий динамически выделенную память, вызывается дважды для одного и того же фрагмента этой памяти. Это неминуемо приведет к ошибке.

Решение проблемы.

Одним из способов обойти такого рода проблемы является создание особого типа конструкторов, - конструкторов копирования. Конструктор копирования или конструктор копии позволяет точно определить порядок создания копии объекта. Любой конструктор копирования имеет следующую форму:

имя_класса (const имя_класса & obj){ ... // тело конструктора}

Здесь obj - это ссылка на объект или адрес объекта. Конструктор копирования вызывается всякий раз, когда создается копия объекта. Таким образом, в конструкторе копирования можно выделить "свою" память под новый объект.

# include <iostream>using namespace std;class ClassName{public: ClassName () { cout << "ClassName!!!\n"; } ClassName (ClassName&obj){ cout << "Copy ClassName!!!\n"; } ~ClassName () { cout << "~ClassName!!!\n"; }};void f(ClassName o){ cout<<"Function f!!!\n";} ClassName r(){ ClassName o; cout<<"Function r!!!\n"; return o;}void main(){ // инициализация одного объекта другим ClassName c1; ClassName c2=c1; // передача объекта в функцию ClassName a; f(a); //возврат объекта из функции r(); } Результат работы программы: // создался объект с1ClassName!!! // инициализация объекта с2 объектом с1Copy ClassName!!! // создался объект аClassName!!! // передача а в функцию по значению// создалась копия оCopy ClassName!!! // отработала функция fFunction f!!! // уничтожилась копия o~ClassName!!! // создался объект o// внутри функции rClassName!!! // отработала функция rFunction r!!! // возврат из функции // создалась копия объекта оCopy ClassName!!! // уничтожился объект o~ClassName!!! // уничтожилась его копия~ClassName!!! // уничтожился объект а~ClassName!!! // уничтожился объект с2~ClassName!!! // уничтожился объект с1~ClassName!!!

Примечание:Конструктор копирования не влияет на операцию присваивания вида A=B. Здесь также срабатывает побитовое копирование, однако эту проблему решают в С++ иначе.

Теперь, когда есть конструктор копирования, можно смело передавать объекты в качестве параметров функций и возвращать объекты. При этом количество вызовов конструкторов будет совпадать с количеством вызовов деструкторов, а поскольку процесс образования копий теперь стал контролируемым, существенно снизилась вероятность неожиданного разрушения объекта. Обратите внимание, что критическая ошибка вероятна только тогда, когда в конструкторе происходит динамическое обращение к памяти с целью выделения, а в деструкторе данная память удаляется.

Примечание:Кстати!!! Помимо создания конструктора копирования есть другой способ организации взаимодействия между функцией и программой, передающей объект. Этот способ - передача объекта по ссылке или по указателю.

Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая

Домашнее задание

  1. Разработать класс Person, который содержит соответствующие члены для хранения:
    • имени,
    • возраста,
    • пола и
    • телефонного номера.

Напишите функции-члены, которые смогут изменять эти члены данных индивидуально. Напишите функцию-член Person::Print(), которая выводит отформатированные данные о человеке.

  1. Разработать класс String, который в дальнейшем будет использоваться для работы со строками. Класс должен содержать:
    • конструктор по умолчанию, позволяющий создать строку длиной 80 символов;
    • конструктор, позволяющий создавать строку произвольного размера;
    • конструктор, который создаёт строку и инициализирует её строкой, полученной от пользователя.

Класс должен содержать методы для ввода строк с клавиатуры и вывода строк на экран.

Предыдущая Оглавление Следующая

 


 

Предыдущая Оглавление Следующая

Урок №22.

  • Константный метод.
  • Пример на создание класса - СТРОКА.
  • Перегрузка операторов.
  • Преобразования, определяемые классом.
  • Пример класса СТРОКА с перегруженными операторами.
  • Домашнее задание
Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая

Константный метод.

Говорят, что метод объекта обладает свойством неизменности (константности), если после его выполнения состояние объекта не изменяется.Если не контролировать свойство неизменности, то его обеспечение будет целиком зависеть от квалификации программиста. Если же "неизменный" метод в процессе выполнения будет производить посторонние эффекты, то результат может быть самым неожиданным,отлаживать и поддерживать такой код очень тяжело.

Язык С++ позволяет пометить метод как константный. При этом неконстантные методы объекта запрещается использовать в теле помеченного метода, и в контексте этого метода ссылки на сам объект и все его поля будут константны. Для обозначения константности, используется модификатор const.

Примечание:Кстати!!! Также существует возможность пометить ссылку (или указатель) как константную. Применительно к ссылке свойство константности означает, что через эту ссылку можно вызывать только константные методы. Присвоение константной ссылки неконстантной запрещено.

Давайте, рассмотрим пример класса с константными методами:

# include <iostream># include <string.h>using namespace std;class Personal{public: // конструктор с параметрами // мы выделяем здесь память // однако в нашем примере нет // ни деструктора, ни конструктора // копирования - единственная цель, // которую мы преследуем показать // работу константного метода Personal(char*p,char*n,int a){ name=new char[strlen(n)+1]; if(!name){ cout<<"Error!!!"; exit(0); } picture_data=new char[strlen(n)+1]; if(!picture_data){ cout<<"Error!!!"; exit(0); } strcpy(picture_data,p); strcpy(name,n); age=a; } // Группа константных методов // внутри них невозможно // изменить какое-то из свойств const char*Name()const{ return name; } int Age()const{ return age; } const char*Picture()const{ return picture_data; } void SetName(const char*n){ strcpy(name,n); } void SetAge(int a){ age=a; } void SetPicture(const char*p){ strcpy(picture_data,p); } private: char*picture_data; // путь к фотографии char*name; // имя int age; // возраст}; void main(){ Personal A("C:\\Image\\","Ivan",23); cout<<"Name: "<<A.Name()<<"\n\n"; cout<<"Age: "<<A.Age()<<"\n\n"; cout<<"Path for picture: "<<A.Picture()<<"\n\n"; A.SetPicture("C:\\Test\\"); A.SetName("Leonid"); A.SetAge(90); cout<<"Name: "<<A.Name()<<"\n\n"; cout<<"Age: "<<A.Age()<<"\n\n"; cout<<"Path for picture: "<<A.Picture()<<"\n\n";}

В данном примере методы Name, Age, Picture объявлены константными. Кроме того, можно наблюдать и использование константных указателей: параметр методов SetName и SetPicture, возвращаемое значение методов Name и Picture. Компилятор обеспечит проверку того, что реализация константных методов не имеет побочных эффектов в виде изменения состояния объекта, реализующего класс Personal. Как только обнаружится попытка выполнить запрещенную операцию, компилятор сообщит об ошибке.

Предыдущая Оглавление Следующая
Предыдущая Оглавление Следующая






©2015 arhivinfo.ru Все права принадлежат авторам размещенных материалов.