Для локальных переменных всегда вызывается деструктор, поэтому есть техники использования этого свойства для некоторых нужд, типа лока мьютекса в конструкторе, и анлока в деструкторе.
Обычные указатели хранят адрес. Для обычных указателей деструктор ничего не делает.
А вот если локальная переменная представляет из себя не указатель, или умный указатель, то в его деструкторе может что-то быть, тем самым объект может высвободить память, либо произвести какие-либо другие манипуляции.
Умные указатели позволяют более гибко управлять памятью. (shared_ptr, unique_ptr...)
Они с технической точки зрения ничем не отличаются от обычных классов (структур).
Им просто заранее ради вашего удобства написали подходящие реализации конструкторов, деструкторов, и других операторов.
После высвобождения памяти адрес не перестаёт быть адресом, поэтому все манипуляции остаются возможными, кроме чтения и записи. Это уже как повезёт. Другое дело, что это не нормальное поведение - читать / писать высвобожденную память. Она уже может быть занята чем-то другим, либо изменена.
Поэтому: никогда не возвращайте ни ссылки, ни адреса локальных переменных, за исключением тех, которые по факту глобальные: например те, которые объявлены как static.
Однако такая static переменная находится в единственном экземпляре, а значит к ней нельзя будет одновременно обратиться из нескольких потоков. Точнее можно, но это не нормально, приведёт к ошибкам.
Ах да, забыл ответить на заголовок.
Точно не знаю, сначала опишу как можно это понимать, а потом уже укажу какую точность не знаю.
Можно это понимать так: вы объявляете в вызывающей функции (caller) локальную переменную, и потом результат вызова присваиваете этой переменной. Эта переменная находится в стеке вызывающей функции (caller).
В функции которую вы вызываете (callee) тоже есть локальная переменная результата.
И после того как вызов закончился, caller присваивает результат от callee через оператор копирования.
То есть
- Конструктор переменной в которую будет возвращён результат в функции из которой происходит вызов (caller). Кроме случая когда вызов является одним из параметров конструктора.
- Конструктор результата в функции которая вызвана (callee)
- Манипуляции с результатом (если есть таковые)
- Оператор копирования, кроме случая когда вызов является одним из параметров конструктора.
Что за конструктор с параметром вызова? Ну например так:
#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 пишут что есть у компиляторов оптимизации которые передают результат по ссылке или указателю, чего-бы это не значило.
И наконец: ссылки ничем не отличаются от указателей в техническом плане. Это просто синтаксический сахар.
Поправьте меня если я где-либо не прав. Буду рад.