Лямбда, про яку йде мова, насправді не має стану .
Вивчіть:
struct lambda {
auto operator()() const { return 17; }
};
А якщо б ми мали lambda f;, це порожній клас. Вищезазначене lambdaфункціонально не лише схоже на вашу лямбду, це (в основному) те, як реалізовано вашу лямбду! (Йому також потрібен неявний привід для функціонування оператора вказівника, і ім'я lambdaбуде замінено на якийсь псевдо- наведений компілятором)
У C ++ об'єкти не є покажчиками. Це справжні речі. Вони використовують лише місце, необхідне для зберігання даних у них. Покажчик на об'єкт може бути більшим за об'єкт.
Хоча ви можете думати про цю лямбду як про вказівник на функцію, це не так. Ви не можете перепризначити auto f = [](){ return 17; };іншу функцію або лямбду!
auto f = [](){ return 17; };
f = [](){ return -42; };
вищезазначене є незаконним . Там немає місця в fв магазині , який функція буде називатися - ця інформація зберігається в типі з f, а не у вартості f!
Якщо ви зробили це:
int(*f)() = [](){ return 17; };
або це:
std::function<int()> f = [](){ return 17; };
ви більше не зберігаєте лямбду безпосередньо. В обох випадках випадки f = [](){ return -42; }є законними, тому в цих випадках ми зберігаємо, яку функцію ми використовуємо у значенні f. І sizeof(f)вже не є 1, а скоріше sizeof(int(*)())чи більшим (в основному, має бути розмір покажчика або більший, як ви очікуєте. std::functionМає мінімальний розмір, передбачений стандартом (вони повинні мати можливість зберігати "всередині себе" виклики до певного розміру), які на практиці принаймні настільки велика, як покажчик функції).
У цьому int(*f)()випадку ви зберігаєте вказівник на функцію, яка поводиться так, ніби ви викликали цю лямбду. Це працює лише для лямбд без громадянства (з пустим []списком захоплення).
У цьому std::function<int()> fвипадку ви створюєте std::function<int()>екземпляр класу видалення типу, який (у цьому випадку) використовує розміщення new для зберігання копії лямбда-розміру 1 у внутрішньому буфері (і, якщо була передана більша лямбда (з більшим станом) ), використовував би розподіл купи).
Як припущення, щось подібне, мабуть, саме на вашу думку відбувається. Що лямбда - це об’єкт, тип якого описується його підписом. У C ++ було вирішено зробити абстракції нульових витрат лямбда за реалізацію об'єкта функції вручну. Це дозволяє передавати лямбда-випромінювання в stdалгоритм (або подібний), а його вміст буде повністю видимим компілятору, коли він створює шаблон алгоритму. Якби лямбда мала тип типу std::function<void(int)>, її вміст був би не повністю видимим, а ручно створений функціональний об’єкт міг би бути швидшим.
Метою стандартизації С ++ є програмування високого рівня з нульовими накладними витратами на ручно створений код С.
Тепер, коли ви розумієте, що fнасправді ви без громадянства, у вас в голові повинно виникнути ще одне запитання: лямбда не має стану. Чому він не має розміру 0?
Існує коротка відповідь.
Усі об'єкти в C ++ повинні мати мінімальний розмір 1 за стандартом, а два об'єкти одного типу не можуть мати однакову адресу. Вони пов'язані, оскільки масив типу Tматиме елементи, розташовані sizeof(T)окремо.
Зараз, оскільки він не має стану, іноді він не може займати місця. Це не може статися, коли воно "одне", але в деяких контекстах це може статися. std::tupleі подібний бібліотечний код використовує цей факт. Ось як це працює:
Оскільки лямбда-еквівалент класу з operator()перевантаженими, лямбди без стану (зі []списком захоплення) - це всі порожні класи. Вони мають sizeofв 1. Насправді, якщо ви успадковуєте від них (що дозволено!), Вони не займуть місця, доки це не спричинить зіткнення адрес одного типу . (Це відомо як оптимізація порожньої бази).
template<class T>
struct toy:T {
toy(toy const&)=default;
toy(toy &&)=default;
toy(T const&t):T(t) {}
toy(T &&t):T(std::move(t)) {}
int state = 0;
};
template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }
sizeof(make_toy( []{std::cout << "hello world!\n"; } ))є sizeof(int)(ну, вище , є незаконним , тому що ви не можете створити лямбда в не-оцінювали контекст: ви повинні створити іменований auto toy = make_toy(blah);то зробити sizeof(blah), але це просто шум). sizeof([]{std::cout << "hello world!\n"; })все ще 1(схожа кваліфікація).
Якщо ми створимо інший тип іграшки:
template<class T>
struct toy2:T {
toy2(toy2 const&)=default;
toy2(T const&t):T(t), t2(t) {}
T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }
тут є дві копії лямбди. Оскільки вони не можуть поділитися однією адресою, sizeof(toy2(some_lambda))це так 2!
structwith anoperator())