Варианты запуска потоков
В прошлом я рассказал, что можно запускать поток не только свободной функцией, но и методом какого-то объекта. Во время публикации этого поста у меня появилось ощущение, что со стороны не совсем понятно, как компилятор различает аргументы функции, саму функцию и объект, из метода которого потенциально поток может быть запущен. Поэтому сегодняшний пост по это.
В С++ есть такое понятие - callable objects. Это просто сущности, которые можно вызвать. Это функции, лямбды, функторы, методы и так далее. Как можно единообразно вызывать все эти сущности? Сходу в голову ничего не приходит, но в стандартной библиотеке начиная с С++17 есть такая функция std::invoke. Вот ее сигнатура:
template< class F, class... Args>
std::invokeresultt<F, Args...> invoke(F&& f, Args&&... args) noexcept
И существуют правила, по которым эта функция парсит свои аргументы. Но не только она, а все сущности, которые как бы "вовлекают" в работу callable объект. Открывайте окна, потому что щас немного духоты будет.
Пусть INVOKE(f, t1, t2, ..., tN) - выражение, обозначающее вовлечение в работу функционального объекта f. Тогда:
Если f - указатель на метод класса T:
t1 - объект типа Т, или ссылка на объект типа Т,
или ссылка на наследника типа Т, то этот INVOKE превращается в (t1.*f)(t2, ..., tN).
t1 - ни что из перечисленного выше(то есть указатель на объект), то INVOKE превращается в ((*t1).*f)(t2, ..., tN).
Если N=1 и f - указатель на член класса:
t1 - объект типа Т, или ссылка на объект типа Т,
или ссылка на наследника типа Т, то этот INVOKE превращается в t1.*f.
t1 - ни что из перечисленного выше(то есть указатель на объект), то INVOKE превращается в (*t1).*f.
Во всех других случая INVOKE разворачивается в обычный вызов функции f(t1, t2, ..., tN).
То есть конструктор std::thread, который внутри себя дергает std::invoke, на основе вот этих простых правил и решает, что ему на самом деле передали и как себя дальше вести.
Пройдитесь глазами по схеме выбора еще раз, чтобы лучше уложить ее себе в голову, потому что она не прям простая. Тут кстати есть отсылочка к моему давнему посту про вызов метода объекта через указатель. Там их целая серия, поэтому, если не смотрели, посмотрите. Получилось очень прикольно, на мой взгляд)
Так что создавайте потоки осознанно и так, как больше подходит именно в вашей ситуации. Потому что способов - тьма.
Stay versatile. Stay cool.
#multitasking
В прошлом я рассказал, что можно запускать поток не только свободной функцией, но и методом какого-то объекта. Во время публикации этого поста у меня появилось ощущение, что со стороны не совсем понятно, как компилятор различает аргументы функции, саму функцию и объект, из метода которого потенциально поток может быть запущен. Поэтому сегодняшний пост по это.
В С++ есть такое понятие - callable objects. Это просто сущности, которые можно вызвать. Это функции, лямбды, функторы, методы и так далее. Как можно единообразно вызывать все эти сущности? Сходу в голову ничего не приходит, но в стандартной библиотеке начиная с С++17 есть такая функция std::invoke. Вот ее сигнатура:
template< class F, class... Args>
std::invokeresultt<F, Args...> invoke(F&& f, Args&&... args) noexcept
И существуют правила, по которым эта функция парсит свои аргументы. Но не только она, а все сущности, которые как бы "вовлекают" в работу callable объект. Открывайте окна, потому что щас немного духоты будет.
Пусть INVOKE(f, t1, t2, ..., tN) - выражение, обозначающее вовлечение в работу функционального объекта f. Тогда:
Если f - указатель на метод класса T:
t1 - объект типа Т, или ссылка на объект типа Т,
или ссылка на наследника типа Т, то этот INVOKE превращается в (t1.*f)(t2, ..., tN).
t1 - ни что из перечисленного выше(то есть указатель на объект), то INVOKE превращается в ((*t1).*f)(t2, ..., tN).
Если N=1 и f - указатель на член класса:
t1 - объект типа Т, или ссылка на объект типа Т,
или ссылка на наследника типа Т, то этот INVOKE превращается в t1.*f.
t1 - ни что из перечисленного выше(то есть указатель на объект), то INVOKE превращается в (*t1).*f.
Во всех других случая INVOKE разворачивается в обычный вызов функции f(t1, t2, ..., tN).
То есть конструктор std::thread, который внутри себя дергает std::invoke, на основе вот этих простых правил и решает, что ему на самом деле передали и как себя дальше вести.
Пройдитесь глазами по схеме выбора еще раз, чтобы лучше уложить ее себе в голову, потому что она не прям простая. Тут кстати есть отсылочка к моему давнему посту про вызов метода объекта через указатель. Там их целая серия, поэтому, если не смотрели, посмотрите. Получилось очень прикольно, на мой взгляд)
Так что создавайте потоки осознанно и так, как больше подходит именно в вашей ситуации. Потому что способов - тьма.
Stay versatile. Stay cool.
#multitasking