Как меняется семантика захвата *this вместо this в лямбде, возвращаемой из метода?
Захват this сохраняет в лямбде указатель на исходный объект, но не продлевает его время жизни. Захват *this создаёт внутри объекта-функции копию текущего объекта, поэтому лямбда не обращается к исходному объекту после его уничтожения.
Лямбды появились в C++11 как способ компактно создавать объекты-функции с захваченным состоянием. Изначально захват this фактически означал захват указателя, что удобно для коротких синхронных вызовов, но опасно для отложенного выполнения.
В C++17 появился явный захват *this, позволяющий сохранить снимок объекта по значению. Он решает задачу безопасной передачи состояния объекта в лямбду, которая может пережить исходный объект.
Метод может вернуть лямбду, передать её в очередь или запустить асинхронно. Если такая лямбда захватила this, а исходный объект был уничтожен раньше вызова лямбды, обращение к членам объекта приводит к неопределённому поведению.
Захват *this устраняет висячий указатель, но меняет семантику: лямбда работает не с актуальным объектом, а с его копией. Изменения исходного объекта после создания лямбды не видны, а изменения копии не влияют на исходный объект.
При захвате this в замыкании хранится указатель. Сам указатель копируется, но объект, на который он указывает, не копируется и не получает дополнительного владения. Поэтому такой захват безопасен только при гарантии, что объект проживёт не меньше лямбды и вызовов через неё.
При захвате *this в замыкании хранится отдельный подобъект, инициализированный текущим объектом. Для этого объект должен быть копируемым; если копирование запрещено, такой захват обычно недоступен. Поля копируются по правилам копирования класса, поэтому внутренние сырые указатели, ссылки и внешние ресурсы не превращаются автоматически в независимые ресурсы.
По умолчанию оператор вызова лямбды является const, поэтому захваченная копия внутри него доступна как константная. Для изменения состояния копии нужен mutable; это всё равно не изменит исходный объект.
Следует учитывать полиморфизм: если *this захватывается в методе базового класса, копируется объект статического типа этого метода. Это может привести к срезке производного состояния. Кроме того, копирование объекта может быть дорогим, а копия состояния с потоками, мьютексами или дескрипторами может иметь нежелательную семантику.
Вызов snapshot() безопасен после уничтожения временного Counter: лямбда хранит собственную копию. Аналогичная лямбда с [this] сохранила бы указатель на уже уничтоженный временный объект.
Сервис формирует задачу для очереди, а затем его объект может быть удалён. Вариант с захватом this прост и не копирует состояние, но создаёт риск обращения к уничтоженному сервису. Захват *this безопаснее по времени жизни, однако копирует весь объект и может захватить устаревшие настройки или некопируемые ресурсы.
Можно захватить только необходимые значения, например идентификатор и параметры задачи. Такой вариант обычно лучше: он явно фиксирует снимок данных, уменьшает размер замыкания и не копирует постороннее состояние объекта. Если же задача должна работать с живым сервисом, используют явное управление временем жизни, например std::shared_ptr или иной владеющий контекст, а не рассчитывают на захват this.
[*this] жизнь исходного объекта?Нет. Оно создаёт независимую копию, но исходный объект может быть уничтожен сразу после создания лямбды. Это безопасно только в отношении доступа к скопированному состоянию; ресурсы, которыми управлял исходный объект, не обязательно становятся независимыми ресурсами копии.
[*this] последующие изменения исходного объекта?Нет. Захват выполняется в момент создания лямбды, поэтому лямбда получает снимок состояния. Если требуется наблюдать актуальные данные, нужно явно организовать совместное состояние, например захватить std::shared_ptr на объект состояния или использовать другой синхронизированный канал доступа.
[*this] для любого класса?Нет. Класс должен поддерживать подходящее копирование, а копия должна иметь корректную семантику для всех своих полей. Особую осторожность требуют сырые указатели, ссылки, мьютексы, файловые дескрипторы, сетевые соединения и указатели на виртуальные объекты: поверхностная копия может сохранить связь с внешним ресурсом или привести к срезке.