@Mr-Governor
Губернирую

C++. Как происходит возвращение локального объекта функциями?

Читаю про конструктор копии. Про передачу аргументов в функцию разобрался, а вот с возвращением локального объекта функцией, запутался.
Есть простой код:
#include <iostream>
using namespace std;

class samp {
public:
	int a; //Данные объекта

	samp()
	{cout << "Конструктор стработал!" << endl;}
	~samp(){cout << "Деструктор стработал!" << endl;	}
};

samp func() //Функция возвращающая локальный объект
{
	samp ob;
	ob.a=255;
	return ob;
}

int main()
{
setlocale(0,"");
	int act;
	do{
		cin>>act;
		samp ob;
		ob = func();
		system("pause"); //Пауза на обдумывание
	}
	while(act);

system("pause");
return 0;
}

Вот результат:

Конструктор стработал!
Конструктор стработал!
Деструктор стработал!
Деструктор стработал!
Для продолжения нажмите любую клавишу . . .
Деструктор стработал!

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

Вроде все нормально.

Но если поменять строчки:
samp ob;
    ob = func();

на
samp *ob;
    ob = &func();


Результат становится таким:

Конструктор стработал!
Деструктор стработал!
Деструктор стработал!
Для продолжения нажмите любую клавишу . . .


Объект создается только в функции, конструктор сработал.
Создается временный объект, а локальный удаляется, деструктор сработал.
Указатель получает адрес..
Стоп.. А какой адрес он получил, временного объекта?
Временный объект уничтожается, деструктор срабатывает.

Выходит, указатель получил адрес на освобожденный временный объект?
Я пробовал записывать в этот объект данные ob->a = 100;
ОС никаких ошибок не выдавала.
Вообщем, помогите пожалуйста разобратся, я запутался :(
  • Вопрос задан
  • 2085 просмотров
Решения вопроса 2
@tomatho
Для локальных переменных всегда вызывается деструктор, поэтому есть техники использования этого свойства для некоторых нужд, типа лока мьютекса в конструкторе, и анлока в деструкторе.

Обычные указатели хранят адрес. Для обычных указателей деструктор ничего не делает.
А вот если локальная переменная представляет из себя не указатель, или умный указатель, то в его деструкторе может что-то быть, тем самым объект может высвободить память, либо произвести какие-либо другие манипуляции.

Умные указатели позволяют более гибко управлять памятью. (shared_ptr, unique_ptr...)
Они с технической точки зрения ничем не отличаются от обычных классов (структур).
Им просто заранее ради вашего удобства написали подходящие реализации конструкторов, деструкторов, и других операторов.

После высвобождения памяти адрес не перестаёт быть адресом, поэтому все манипуляции остаются возможными, кроме чтения и записи. Это уже как повезёт. Другое дело, что это не нормальное поведение - читать / писать высвобожденную память. Она уже может быть занята чем-то другим, либо изменена.

Поэтому: никогда не возвращайте ни ссылки, ни адреса локальных переменных, за исключением тех, которые по факту глобальные: например те, которые объявлены как static.
Однако такая static переменная находится в единственном экземпляре, а значит к ней нельзя будет одновременно обратиться из нескольких потоков. Точнее можно, но это не нормально, приведёт к ошибкам.

Ах да, забыл ответить на заголовок.
Точно не знаю, сначала опишу как можно это понимать, а потом уже укажу какую точность не знаю.

Можно это понимать так: вы объявляете в вызывающей функции (caller) локальную переменную, и потом результат вызова присваиваете этой переменной. Эта переменная находится в стеке вызывающей функции (caller).
В функции которую вы вызываете (callee) тоже есть локальная переменная результата.
И после того как вызов закончился, caller присваивает результат от callee через оператор копирования.
То есть
  1. Конструктор переменной в которую будет возвращён результат в функции из которой происходит вызов (caller). Кроме случая когда вызов является одним из параметров конструктора.
  2. Конструктор результата в функции которая вызвана (callee)
  3. Манипуляции с результатом (если есть таковые)
  4. Оператор копирования, кроме случая когда вызов является одним из параметров конструктора.

Что за конструктор с параметром вызова? Ну например так:
#include <string>
int meaning_of_life()
{
  return 42;
}

std::string wat()
{
  return "wat"; // тут будет вызван std::string("wat")
}

int main()
{
  int a(meaning_of_life()); // тоже самое, что и int a = meaning_of_life();
  std::string s(wat()); // тоже самое, что и std::string s = wat();
  return 0;
}

В таком случае результат используется прямо в конструкторе, поэтому оператор копирования не нужен. В таком случае конструктор называется конструктором копирования.

По поводу точностей. Не знаю как стандарт регламентирует где должен и как передаваться результат. А так же не знаю, есть ли оптимизации которые пишут из callee прямо в caller.
На StackOverflow пишут что есть у компиляторов оптимизации которые передают результат по ссылке или указателю, чего-бы это не значило.

И наконец: ссылки ничем не отличаются от указателей в техническом плане. Это просто синтаксический сахар.

Поправьте меня если я где-либо не прав. Буду рад.
Ответ написан
TrueBers
@TrueBers
Гуглю за еду
samp *ob;
    ob = &func();


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

Включите все уровни предупреждений в вашем компиляторе и он вам скажет, что вы неправильно делаете. По крайней мере clang и gcc об этом скажут по умолчанию. Думаю, что и вижуал студия справится.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы