Возвращение значений с помощью ссылок
Несмотря на то что листинг 9.8 прекрасно работает, его можно упростить как для чтения, так и в эксплуатации, если вместо указателей использовать ссылки. В листинге 9.9 показана та же самая программа, но вместо указателей в качестве параметров функции в ней используются ссылки, а также добавлено упомянутое выше перечисление ERROR.
Листинг 9.9. Возвращение значений с помощью ссылок
1: // Листинг 9.9.
2: // Возвращение нескольких значений из функции
3: // с помощью ссылок
4:
5: #include <iostream.h>
6:
7: typedef unsigned short USHORT;
8: enum ERR_CODE { SUCCESS, ERROR }
9:
10: ERR_CODE Factor(USHORT, USHORT&, USHORT&);
11:
12: int main()
13: {
14: USHORT number, sguared, cubed;
15: ERR__CODE result;
16:
17: cout << "Enter а number (0 - 20): ";
18: cin >> number;
19:
20: result = Factor(number, squared, cubed);
21:
22: if (result == SUCCESS)
23: {
24: cout << "number: " << number << "\n";
25: cout << "square: " << squared << "\n";
26: cout << "cubed: " << cubed << "\n";
27: }
28: else
29: cout << "Error encountered!!\n";
30: return 0;
31: }
32:
33: ERR_CODE Factor(USHORT n, USHORT &rSquared, USHORT &rCubed)
34: {
35: if (n > 20)
36: return ERROR; // simple error code
37: else
38: {
39: rSquared = n*n;
40: rCubed = n*n*n;
41: return SUCCESS;
42: }
43: }
Результат:
Enter a number (0 - 20): 3
number: 3
square: 9
cubed: 27
Анализ: Листинг 9.9 идентичен листингу 9.8 с двумя исключениями. Перечисление ERR_CODE делает сообщение об ошибке более явным (см. строки 36 и 41), как, впрочем, и его обработку (строка 22).
Однако более существенные изменения коснулись функции Factor(). Теперь эта функция объявляется для принятия не указателей, а ссылок на переменные squared и cubed, что делает манипуляции над этими параметрами гораздо проще и легче для понимания.
Передача ссылок на переменные как средство повышения эффективности
При каждой передаче объекта в функцию как значения создается копия этого объекта. При каждом возврате объекта из функции создается еще одна копия.
На занятии 5 вы узнали о том, что эти объекты копируются в стек и на этот процесс расходуется время и память. Для таких маленьких объектов, как базовые целочисленные значения, цена этих расходов незначительна.
Однако для больших объектов, создаваемых пользователем, расходы ресурсов существенно возрастают. Размер такого объекта в стеке представляет собой сумму всех его переменных-членов. Причем каждая переменная-член может быть, в свою очередь, подобным объектом, поэтому передача такой массивной структуры путем копирования в стек может оказаться весьма дорогим удовольствием как по времени, так и по занимаемой памяти.
Кроме того, существуют и другие расходы. При создании временных копий объектов классов для этих целей компилятор вызывает специальный конструктор-копировщик. ,Ha следующем занятии вы узнаете, как работают конструкторы-копировщики и как можно создать собственный конструктор-копировщик, но пока достаточно знать, что конструктор-копировщик вызывается каждый раз, когда в стек помещается временная копия объекта.
При разрушении временного объекта, которое происходит при возврате из функции, вызывается деструктор объекта. Если объект возвращается функцией как значение, копия этого объекта должна быть сначала создана, а затем разрушена.
При работе с большими объектами эти вызовы конструктора и деструктора могут оказать слишком ощутимое влияние на скорость работы программы и использование памяти компьютера. Для иллюстрации этой идеи в листинге 9.10 создается пользовательский объект SimpleCat. Реальный объект имел бы размеры побольше и обошелся бы дороже, но и этого примера вполне достаточно, чтобы показать, насколько часто вызываются конструктор-копировщик и деструктор.
Итак, в листинге 9.10 создается объект SimpleCat, после чего вызываются две функции. Первая функция принимает объект Cat как значение, а затем возвращает его как значение. Вторая же функция принимает указатель на объект, а не сам объект, и возвращает указатель на объект.
Листинг 9.10. Передача объектов как ссылок с помощью указателей
1: // Листинг 9.10.
2: // Передача указателей на объекты
3:
4: #include <iostream.h>
5:
6: class SimpleCat
7: {
8: public:
9: SimpleCat (); // конструктор
10: SimpleCat(SimpleCat&); // конструктор-копировщик
11: ~SimpleCat(); // деструктор
12: };
13:
14: SimpleCat::SimpleCat()
15: {
16: cout << "Simple Cat Constructor...\n";
17: }
18:
19: SimpleCat::SimpleCat(SimpleCat&)
20: {
21: cout << "Simple Cat Copy Constructor...\n";
22: }
23:
24: SimpleCat::~SimpleCat()
25: {
26: cout << "Simple Cat Destructor...\n";
27: }
28:
29: SimpleCat Function0ne (SimpleCat theCat);
30: SimpleCat* FunctionTwo (SimpleCat *theCat);
31:
32: int main()
33: {
34: cout << "Making a cat,.,\n";
35: SimpleCat Frisky;
36: cout << "Calling FunctionOne,,,\n";
37: FunctionOne(Frisky);
38: cout << "Calling FunctionTwo..,\n";
39: FunctionTwo(&Frisky);
40: return 0;
41: }
42:
43: // Функция FunctionOne, передача как значения
44: SimpleCat FunctionOne(SimpleCat theCat)
45: {
46: cout << "Function One. Roturning,,,\ri";
47: return theCat;
48: }
49:
50: // Функция FunctionTwo, передача как ссылки
51: SimpleCat* FunctionTwo (SimpleCat *theCat)
52: {
53: cout << "Function Two. Returning...\n";
54: return theCat;
55: }
Результат:
Making a cat...
Simple Cat Constructor...
Calling FunctionOne...
Simple Cat Copy Constructor...
Function One. Returning...
Simple Cat Copy Constructor...
Simple Cat Destructor...
Simple Cat Destructor...
Calling FunctionTwo...
Function Two. Returning...
Simple Cat Destructor...
Примечание: Номера строк не выводятся. Мы добавили их для удобства проведения анализа программы .
Анализ: В строках 6-12 объявляется весьма упрошенный класс SimpleCat. Конструктор, конструктор-копировщик и деструктор — все компоненты класса выводят на экран свои информативные сообщения, чтобы было точно известно, когда они вызываются.
В строке 34 функция main() выводит свое первое сообщение, которое является первым и в результатах работы программы. В строке 35 создается экземпляр объекта класса SimpleCat. Это приводит к вызову конструктора, что подтверждает сообщение, выводимое этим конструктором (строка 2 в результатах работы программы).
В строке 36 функция main() "докладывает" о вызове функции FunctionOne, которая выводит свое сообщение (строка 3 в результатах работы программы). Поскольку функция FunctionOne() вызывается с передачей объекта класса SimpleCat по значению, в стек помещается копия объекта SimpleCat как локального для вызываемой функции. Это приводит к вызову конструктора копии, который "вносит свою лепту" в результаты работы программы (сообщение в строке 4).
Выполнение программы переходит к строке 46, которая принадлежит телу вызванной функции, выводящей свое информативное сообщение (строка 5 в результатах работы программы). Затем эта функция возвращает управление программой вызывающей функции, и объект класса SimpleCat вновь возвращается как значение. При этом создается еще одна копия объекта за счет вызова конструктора-копировщика и, как следствие, на экран выводится очередное сообщение (строка 6 в результатах работы программы).
Значение, возвращаемое из функции FunctionOne(), не присваивается ни одному объекту, поэтому ресурсы, затраченные на создание временного объекта при реализации механизма возврата, просто выброшены на ветер, как и ресурсы, затраченные на его удаление с помощью деструктора, который заявил о себе в строке 7 в результатах работы программы. Поскольку функция FunctionOne() завершена, локальная копия объекта выходит за область видимости и разрушается, вызывая деструктор и генерируя тем самым сообщение, показанное в строке 8 в результатах работы программы.
Управление программой возвращается к функции main(), после чего вызывается функция FunctionTwo(), но на этот раз параметр передается как ссылка. При этом никакой копии объекта не создается, поэтому отсутствует и сообщение от конструктора- копировщика. В функции FunctionTwo() выводится сообщение, занимающее строку 10 в результатах работы программы, а затем выполняется возвращение объекта класса SimpleCat (снова как ссылки), поэтому нет никаких обращений к конструктору и деструктору.
Наконец программа завершается и объект Frisky выходит за область видимости, генерируя последнее обращение к деструктору, выводящему свое сообщение (строка 11 в результатах работы программы).
Проанализировав работу этой программы, можно сделать вывод, что при вызове функции FunctionOne() делается два обращения к конструктору копии и два обращения к деструктору, поскольку объект в эту функцию передается как значение, в то время как при вызове функции FunctionTwo() подобных обращений не делается.