Рассмотрим одну из основных задач, встречающихся в программах на C++. Пусть требуется вернуть объект из функции.
Проще всего сделать копию:
1 2 3 4 5 6 7 |
class MyClass { // ... }; MyClass func1() { return MyClass(); } |
Это работает, но если объект большой, то мы теряем время на копирование. Лучше вернуть значение через указатель на объект:
1 2 3 |
MyClass* func2() { return new MyClass(); } |
Оператор new выделяет память, инициализирует объект и возвращает его адрес. Вот как можно использовать эту функцию:
1 2 3 |
MyClass* c = func2(); // ... delete c; |
Обратите внимание, что мы явно вызываем оператор delete, когда объект, на который указывает указатель, больше не нужен. После вызова объект разрушается, а память помечается, как незанятая.
РЕКОМЕНДУЕМ: Пять правил оптимизации программ
Если память не освобождать, то можно всю ее израсходовать. В этом случае новые объекты создать не получится. Но не бойтесь: после завершения работы приложения (возможно, аварийного) память в любом случае освобождается и попадает под управление операционной системы.
У вас мог возникнуть вопрос: «А зачем создавать объект с помощью new, если можно вернуть адрес объекта»? Примерно так:
1 2 3 4 |
MyClass* func3() { MyClass c; return &c; } |
Этот код скомпилируется (зависит от настроек компилятора, поэтому не гарантирую). Но вы наверняка увидите предупреждение. Вот почему: в этом фрагменте происходит возврат адреса локальной переменной, которая будет уничтожена после выхода из функции.
Но ведь мы до этого спокойно вернули объект из func1(), почему же там все было в порядке? Причина в области видимости переменной.
Оператор new выделяет память для переменных в куче. Под кучей понимают общую в рамках процесса память. С ней ничего не случится, если только не освободить выделенную для переменной память с помощью delete.
Память для локальных переменных выделяется в стеке. Стек существенно меньше кучи, поэтому локальные переменные в нем надолго не задерживаются.
В большинстве случаев правило определения времени жизни и области видимости локальных переменных довольно простое: локальная переменная существует только внутри фигурных скобок:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void func() { int x = 0; { // Здесь x видна int y = x; } // x все еще видна // А вот y уже вышла из области видимости // x = y; - ОШИБКА! } // Вне функции мы не можем использовать ни x, ни y |
То, что происходит при вызове func3(), равносильно следующему:
1 2 3 4 5 6 7 8 9 |
MyClass* pC = nullptr; // nullptr - ничто { MyClass c; pC = &c; } // Объект c уже уничтожен, поэтому pC указывает на адрес, который БЫЛ выделен для c, но в этом месте уже недействителен // *pC; - ОШИБКА! Код может сработать, но надеяться на это не следует |
В случае же использования func1() все нормально (похоже, но не равносильно):
1 2 3 4 5 6 7 8 |
MyClass c1; { MyClass c2; c1 = c2; } // Здесь с2 уже не существует, но мы успели сделать копию, поэтому содержимое теперь в c1 |
Выводы
Если требуется вернуть из функции переменную примитивного типа, то используйте обычное копирование (как в func1()). Объекты создавайте с помощью new и возвращайте через указатель (как в func2()).
Будьте осторожны при работе с указателями! Если указатель указывает на локальную переменную, которая уже вышла из области видимости, то он становится некорректным. Пользоваться им нельзя, но компилятор вам об этом не скажет.
РЕКОМЕНДУЕМ: Основные структуры данных в C++