IT Notes

Область видимости переменных в C++

Рассмотрим одну из основных задач, встречающихся в программах на C++. Пусть требуется вернуть объект из функции.

Проще всего сделать копию:

class MyClass {
    // …
};

MyClass func1() {
    return MyClass();
}

Это работает, но если объект большой, то мы теряем время на копирование. Лучше вернуть значение через указатель на объект:

MyClass* func2() {
    return new MyClass();
}

Оператор new выделяет память, инициализирует объект и возвращает его адрес. Вот как можно использовать эту функцию:

MyClass* c = func2();
// …
delete c;

Обратите внимание, что мы явно вызываем оператор delete, когда объект, на который указывает указатель, больше не нужен. После вызова объект разрушается, а память помечается, как незанятая.

Мебель из дерева на заказ здесь еще больше.

Если память не освобождать, то можно всю ее израсходовать. В этом случае новые объекты создать не получится. Но не бойтесь: после завершения работы приложения (возможно, аварийного) память в любом случае освобождается и попадает под управление операционной системы.

У вас мог возникнуть вопрос: "А зачем создавать объект с помощью new, если можно вернуть адрес объекта"? Примерно так:

MyClass* func3() {
    MyClass c;
    return &c;
}

Этот код скомпилируется (зависит от настроек компилятора, поэтому не гарантирую). Но вы наверняка увидите предупреждение. Вот почему: в этом фрагменте происходит возврат адреса локальной переменной, которая будет уничтожена после выхода из функции.

Но ведь мы до этого спокойно вернули объект из func1(), почему же там все было в порядке? Причина в области видимости переменной.

Оператор new выделяет память для переменных в куче. Под кучей понимают общую в рамках процесса память. С ней ничего не случится, если только не освободить выделенную для переменной память с помощью delete.

Память для локальных переменных выделяется в стеке. Стек существенно меньше кучи, поэтому локальные переменные в нем надолго не задерживаются.

В большинстве случаев правило определения времени жизни и области видимости локальных переменных довольно простое: локальная переменная существует только внутри фигурных скобок:

void func() {
    int x = 0;

    {
        // Здесь x видна
        int y = x;
    }

    // x все еще видна
    // А вот y уже вышла из области видимости
    // x = y; - ОШИБКА!
}

// Вне функции мы не можем использовать ни x, ни y

То, что происходит при вызове func3(), равносильно следующему:

MyClass* pC = nullptr; // nullptr - ничто

{
    MyClass c;
    pC = &c;
}

// Объект c уже уничтожен, поэтому pC указывает на адрес, который БЫЛ выделен для c, но в этом месте уже недействителен
// *pC; - ОШИБКА! Код может сработать, но надеяться на это не следует

В случае же использования func1() все нормально (похоже, но не равносильно):

MyClass c1;

{
    MyClass c2;
    c1 = c2;
}

// Здесь с2 уже не существует, но мы успели сделать копию, поэтому содержимое теперь в c1

Выводы

Если требуется вернуть из функции переменную примитивного типа, то используйте обычное копирование (как в func1()). Объекты создавайте с помощью new и возвращайте через указатель (как в func2()).

Будьте осторожны при работе с указателями! Если указатель указывает на локальную переменную, которая уже вышла из области видимости, то он становится некорректным. Пользоваться им нельзя, но компилятор вам об этом не скажет.

Похожие публикации

Комментарии

Очень интересно

спасибо за статью

Anonymous:

спасибо за статью

Пожалуйста :)

)