Достаточно простой пример:
1 class SomeClass 2 { 3 public: 4 SomeClass() 5 { 6 m_SomeFunctor = [this]() {this->Foo();} 7 } 8 9 void Foo() {;} 10 void Bar() {m_SomeFunctor();} 11 12 private: 13 std::function<void ()> m_SomeFunctor; 14 }; 15 16 SomeClass MakeClassInstance() 17 { 18 SomeClass cls; 19 20 cls->Foo(); 21 22 return cls; 23 } 24 25 //... 26 27 SomeClass cls = MakeSomeClass(); 28 cls.Bar(); // oops!!!
Тут происходит одна интересная, но неочевидная (на первый взгляд) вещь. В конструкторе создаётся лямбда-функция, в списке захвата которой находится this. Экземпляр класса создаётся с помощью порождающей функции (MakeClassInstance), после чего в строке 28 происходит неприятность. Из за того, что работаем мы, фактически, с копией созданного внутри MakeClassInstance объекта, указатель на SomeClass, сохранённый внутри лямбда-функции, является невалидным.
Будьте осторожнее!
да, с++ в своем репертуаре...
ОтветитьУдалитьвместе с тортиком в виде лямбды поставляются кусочки стекла, так что поедание тортика требует высокого профессионализма :-)
иногда можно исправить так:
const SomeClass& cls = MakeSomeClass();
кстати, тут RVO вроде применимо? если сработает, то this останется валидным, так?
p.s. а нафига у тебя капча для комментаторов?
Я не к тому клоню, что "с RVO все будет хорошо", а наоборот -- с RVO можно этот баг и не заметить
ОтветитьУдалитьимя, ну, можно и так сказать. С другой стороны - такими "кусочками стекла" весь C++ нашпигован по самое небалуйся. Возможностей отстрелить себе ногу - хоть отбавляй. Просто надо быть аккуратнее. Да и такая особенность лямбды напрямую вытекает из общей архитектуры языка.
ОтветитьУдалитьИ таки да - RVO может скрыть этот косяк.
ЗЫ: Каптчу отключил.
насчет "общей архитектуры языка" -- понятно, про эффективность
ОтветитьУдалитья так понимаю, лямбды создавались под локальное использование, а нелокальные лямбды, кмк, нужно создавать так, чтобы захваченные аргументы копировались -- в самом деле, мы ведь не собираемся организовывать скрытые каналы связи и прочую лабуду из серии "сэмулируем ооп на лямбдах"?
если они будут копироваться, то и проблем не будет
только вот мне не ясно, как это сделать в твоем случае... и вообще не ясно, что планировалось
еще раз -- назначение SomeClass не ясно
ОтветитьУдалитья конечно подозреваю, что SomeClass предполагался, например, как сумматор, из которого можно достать сумму, и который для удобства сразу предлагает нужную для суммирования лямбду... но лучше ты сам напиши
а еще мне сильно кажется, что вообще RVO должно быть не оптимизацией, а единственным возможным вариантом, если RVO возможно
ОтветитьУдалить"я так понимаю, лямбды создавались под локальное использование, а нелокальные лямбды, кмк, нужно создавать так, чтобы захваченные аргументы копировались -- в самом деле, мы ведь не собираемся организовывать скрытые каналы связи и прочую лабуду из серии "сэмулируем ооп на лямбдах"?"
ОтветитьУдалитьНе обязательно. И не только для этого. Например:
class Dispatcher
{
//...
boost::signal OnSomething;
//...
};
void SomeClass::Subscribe(Dispatcher* disp)
{
disp->OnSomething.connect([this]() {DoSomething();}
}
Вполне себе нелокальная лямбда.
"я конечно подозреваю, что SomeClass предполагался, например, как сумматор, из которого можно достать сумму, и который для удобства сразу предлагает нужную для суммирования лямбду... но лучше ты сам напиши"
Примерно так, только не сумматор, а что-то вроде projection iterator. В шаблонном конструкторе создаётся лямбда, заточенная на итерирование конкретного типа последовательности. Часть стейта лямбда хранит во внешнем классе (который и передаётся по this). По-хорошему, перепроектировать надо. Но тут надо будет хорошо голову поломать, потому что лишний раз играться с динамической памятью тоже не хочется.
"а еще мне сильно кажется, что вообще RVO должно быть не оптимизацией, а единственным возможным вариантом, если RVO возможно "
:) Ну да. :)