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;
Martin York

1
Відносяться для константних місцевих жителів: stackoverflow.com/questions/2784262 / ...
Чіро Сантіллі郝海东冠状病六四事件法轮功

Відповіді:


193

Цей фрагмент коду:

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();    
}

Цікаво, що прив’язання тимчасового до посилання const є цілком законним 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();  
}

2
Красиве пояснення. : hattip: У третьому фрагменті коду ви видаляєте int* p = func2(); delete p;Тепер, коли ви видалили "p", чи означає це, що пам'ять, виділене "всередині", визначення функції func2()також видалено?
Водолій_Гирл

2
@Anisha Kaul: Так. Пам'ять була розподілена всередині func2()і випущена назовні в наступному рядку. Це досить сприйнятливий до помилок спосіб обробки пам’яті, хоча, як я вже сказав, ви використовуєте якийсь варіант RAII. До речі, вам здається, що ви вивчаєте C ++. Я рекомендую підібрати хорошу вступну книгу C ++, з якої можна навчитися. Крім того, для подальшого ознайомлення, якщо у вас виникне запитання, ви завжди можете розмістити це питання на переповнюванні стека. Коментарі не призначені для задачі абсолютно нових питань.
In silico

Тепер я зрозумів, ти зробив це правильно! Функція повертала вказівник, і поза цією функцією ви видалили пам'ять, на яку вона вказувала. Зараз зрозуміло, і дякую за посилання.
Aquarius_Girl

і ви відредагували відповідь ?? : mad: Я міг це легко пропустити. ;);)
Водолій_Гірл

@Anisha Kaul: Ні, я цього не зробив. Востаннє я редагував свою відповідь 10 січня відповідно до позначки часу під моєю посадою.
In silico

18

Локальна змінна - це пам'ять на стеку, що пам'ять не вимикається автоматично, коли ви виходите за межі області. З функції, глибше вкладеної (вище на стеку в пам'яті), її ідеально безпечний доступ до цієї пам'яті.

Як тільки функція повертається і закінчується, все стає небезпечним. Зазвичай пам'ять не видаляється і не перезаписується, коли ви повертаєтесь, тобто пам'ять, на якій адреса все ще містить ваші дані - покажчик здається дійсним.

Поки інша функція не нарощує стек і перезаписує його. Ось чому це може працювати деякий час - а потім раптом перестає функціонувати після того, як один особливо глибоко вкладений набір функцій, або функція з дійсно величезними розмірами або безліччю локальних об'єктів, знову досягає цієї стекової пам'яті.

Може навіть статися, що ви знову дістанетесь до тієї ж програмної частини та замініть свою стару локальну змінну функції новою змінною функції. Все це дуже небезпечно і його слід сильно відмовляти. Не використовуйте покажчики на локальні об’єкти!


2

Добре пам’ятати ці прості правила, і вони стосуються як параметрів, так і типів повернення ...

  • Значення - робить копію предмета, про який йде мова.
  • Покажчик - позначає адресу предмета, про який йде мова.
  • Довідка - це буквально предмет, про який йде мова.

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

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

Це може безпосередньо змінити значення переданого параметра. В той час як цей код ...

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

не хотів би. Це просто змінить значення oValueлокального на виклик функції. Причина цього полягає в тому, що ви насправді змінюєте лише "локальну" копію oValue, а не oValueсебе.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.