Лямбда, про яку йде мова, насправді не має стану .
Вивчіть:
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
!
struct
with anoperator()
)