C ++ Возврат ссылки на локальную переменную

117

Правильный ли следующий код (func1 ()), если он должен возвращать i? Я помню, как где-то читал, что возникает проблема при возврате ссылки на локальную переменную. Чем он отличается от func2 ()?

int& func1()
{
    int i;
    i = 1;
    return i;
}

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}
блицкриг
источник
1
Если вы измените func1 () на использование динамически выделяемой памяти, они останутся такими же :-)int& i = * new int;
Мартин Йорк
1
Связано с местными жителями const: stackoverflow.com/questions/2784262/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Ответы:

194

Этот фрагмент кода:

int& func1()
{
    int i;
    i = 1;
    return i;
}

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

int main()
{
    int& p = func1();
    /* p is garbage */
}

Вторая версия работает, потому что переменная размещается в бесплатном хранилище, которое не привязано к времени жизни вызова функции. Однако вы несете ответственность за deleteвыделенный int.

int* func2()
{
    int* p;
    p = new int;
    *p = 1;
    return p;
}

int main()
{
    int* p = func2();
    /* pointee still exists */
    delete p; // get rid of it
}

Обычно вы помещаете указатель в какой-либо класс RAII и / или фабричную функцию, чтобы вам не приходилось делать deleteэто самостоятельно.

В любом случае вы можете просто вернуть само значение (хотя я понимаю, что приведенный вами пример, вероятно, был надуманным):

int func3()
{
    return 1;
}

int main()
{
    int v = func3();
    // do whatever you want with the returned value
}

Обратите внимание, что совершенно нормально возвращать большие объекты таким же образом, как и func3()примитивные значения, потому что практически каждый компилятор в настоящее время реализует ту или иную форму оптимизации возвращаемого значения :

class big_object 
{ 
public:
    big_object(/* constructor arguments */);
    ~big_object();
    big_object(const big_object& rhs);
    big_object& operator=(const big_object& rhs);
    /* public methods */
private:
    /* data members */
};

big_object func4()
{
    return big_object(/* constructor arguments */);
}

int main()
{
     // no copy is actually made, if your compiler supports RVO
    big_object o = func4();    
}

Интересно, что привязка временной ссылки к константной ссылке совершенно допустима в C ++ .

int main()
{
    // This works! The returned temporary will last as long as the reference exists
    const big_object& o = func4();    
    // This does *not* work! It's not legal C++ because reference is not const.
    // big_object& o = func4();  
}
In silico
источник
2
Красивое объяснение. : hattip: В третьем фрагменте кода вы удаляете int* p = func2(); delete p;Теперь, когда вы удалили 'p', означает ли это, что память, выделенная «внутри» определения функции func2(), также была удалена?
Aquarius_Girl
2
@ Аниша Кауль: Да. Память была выделена внутри func2()и выпущена снаружи в следующей строке. Это довольно подверженный ошибкам способ работы с памятью, как я уже сказал, вместо этого вы бы использовали какой-нибудь вариант RAII. Кстати, вы говорите так, будто изучаете C ++. Я рекомендую взять хорошую вводную книгу по C ++, чтобы учиться. Кроме того, для справки в будущем, если у вас есть вопрос, вы всегда можете опубликовать его на сайте Stack Overflow. Комментарии не предназначены для того, чтобы задавать совершенно новые вопросы.
In silico
Теперь я понял, вы все сделали правильно! Функция возвращала указатель, и за пределами этой функции вы удалили память, на которую он указывал. Теперь понятно, и спасибо за ссылку.
Aquarius_Girl
а ответ вы редактировали ?? : mad: Я легко мог это пропустить. ;);)
Aquarius_Girl
@ Аниша Кауль: Нет, не знал. Последний раз я редактировал свой ответ 10 января, согласно отметке времени под моим сообщением.
In silico
18

Локальная переменная - это память в стеке, эта память не становится автоматически недействительной при выходе из области видимости. Из более глубоко вложенной функции (выше в стеке в памяти) доступ к этой памяти совершенно безопасен.

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

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

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

цицеро
источник
2

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

  • Стоимость - делает копию предмета, о котором идет речь.
  • Указатель - относится к адресу рассматриваемого объекта.
  • Справка - это буквально предмет, о котором идет речь.

Для каждого есть свое время и место, поэтому обязательно познакомьтесь с ними. Локальные переменные, как вы здесь показали, ограничены временем, в течение которого они локально активны в области действия функции. В вашем примере наличие типа возврата int*и возврата &iбыло бы одинаково неверным. В таком случае тебе лучше сделать это ...

void func1(int& oValue)
{
    oValue = 1;
}

Это напрямую изменит значение переданного вами параметра. А этот код ...

void func1(int oValue)
{
    oValue = 1;
}

не стал бы. Это просто изменит значение oValuelocal для вызова функции. Причина этого в том, что вы фактически меняете только «локальную» копию oValue, а не oValueсаму себя.

Давид Сумич
источник