Комментарии 16
Ещё б документация была б хорошая, а не вот это месиво доксигена. Ну и в туториалах на gh кросс-ссылки и вычитку английского, а то встречаются там местами штуки типа "бесплатных функций" (free functions).
Приношу свои извинения за качество английского. Но, насколько мне известно, термин free functions в C++ вполне себе имеет место быть.
Я понимаю, что вы имели ввиду, но предварительно погуглив я так и не нашёл, чтобы термин был достаточно расхожим именно в контексте свободных функций, да и ссылается он обычно на методы освобождения памяти, либо в контексте производительности в смысле, что их использование практически бесплатное. Для понимания это не критично, но выглядит как несколько бесполезное уточнение в любом случае. Вощем почитать-поревьювить имеет смысл.
Возможно, имелись в виду свободные функции, т. е. не являющиеся членами класса?
Именно они.
Именно они и имелись ввиду. Только в контексте использования фразы это как бы не имеет принципиального значения. Как говорится найдите пять отличий.
class Foo {
public:
static void bar() {/*some code*/}
};
namespace Foo {
void bar() {/*some code*/}
}
Первое -- это static method или static member function.
Второе -- это free-standing или просто free function.
Отличия вполне себе принципиальные (в определенных контекстах). Например, когда мы используем policy-based подход и параметризуем какой-то кусок код типом policy:
template<class Policy>
struct some_worker {
void do_something() {
auto * buffer = Policy::take_buffer();
...
Policy::dump_buffer(buffer);
}
};
В случае static methods мы можем иметь несколько разных классов Policy с разными реализациями take_buffer
и dump_buffer
. Соответственно, можем параметризовать ими some_worker
.
Тогда как свободные функции в разных пространствах имен не дают нам такой возможности, мы не можем в C++ параметризовать тип пространством имен.
Параметризовать иногда бывает полезно, только в том контексте в котором они сейчас там используется в той документации, что я кинул это не играет никакой роли.
Вроде как термин "free functions" достаточно широко используется в C++ сообществе в качестве синонима для "free-standing functions".
Например, у Страуструпа в C++11 FAQ: "Member functions can be treated as free functions with an extra argument".
Вот рекомендация с SonarSource.com: "Free functions should be preferred to member functions when accessing a container in a generic context"
Вот из пропозала про Extension Methods for C++: "There have been proposals [N1585, N4165, N4174, N4474] suggesting changes to C++’s function resolution rules to allow free function invocation syntax to invoke member functions and vice-versa."
Вот из C++ Core Guidelines: "Code can call the .at() member function on each class, which will result in an std::out_of_range exception being thrown. Alternatively, code can call the at() free function, which will result in fail-fast (or a customized action) on a bounds violation."
И т.д., и т.п.
Стоит отметить, что в таких случаях они используются как противопоставление member functions.
Я не пользователь библиотеки, а обычный мимокрокодил, искушенный всякими ржавыми (пример)/питонячьими(пример) документациями. Последняя доксигеновская дока с которой я работал была у cocos2d и это был такой себе опыт. Для новичка текущей документации будет мало, что увеличивает порог входа к использованию вашей библиотеки. Поэтому ещё раз - менять/улучшать документацию/вики или нет - дело ваше, я не настаиваю.
SObjectizer - очень интересный framework, слежу краем глаза лет 7 уже.
Единственное, что я явно не увидел во всех пропагандистских (в хорошем смысле) статьях - что в нем насчет динамического распределения памяти?.. Для моей области применения это очень критично, - использование стандартных new/delete недопустимо (дает просадку перформанса до 20%, причем вне зависимости, что прикручиваешь в качестве системного аллокатора - tcmalloc, jemalloc и пр., разница только в незначительном различии процентов этой просадки). Поэтому либо преаллоцированные массивы, либо страничные thread-local субаллокаторы без блокировок (строго говоря, на fast path).
Выпиливание new/delete из бибилиотек - вещь неблагодарная, зачастую невозможная (получится клон, новые версии применять невозможно). Предусмотрена ли в SObjectizer обертка над распределением памяти?..
Динамическая память используется самым активнейшим образом. Фактически, за всеми основными операциями, как то регистрация кооперации, создание подписки, отсылка сообщения, скрывается выделение памяти.
Это следствие того, что SObjectizer создавался под задачи, где требовалась динамика: т.е. агенты могли создаваться и уничтожаться в процессе работы. А раз так, то заморачиваться на то, чтобы можно было для всех ключевых операций использовать преаллоцированные массивы, смысла не было.
По сути, наиболее критическими являются следующие штуки:
отсылка сообщения. Сообщение в SO5 -- это динамически созданный объект, время жизни которого регулируется через подсчет ссылок (на основе atomic-ов). В принципе, в SO5 можно использовать преаллоцированные сообщения. Хотя это и недостаточно удобно. И я не знаю, используется ли эта фича где-то в дикой природе;
постановка заявки на обработку сообщения и удаление этой заявки. Это все определяется диспетчером. Штатные диспетчеры тут особо не заморачиваются, используют либо STL-ные контейнеры (естественно с перераспределением памяти когда это нужно), либо собственные динамические списки (естественно с new/delete). В принципе, можно сделать свой диспетчер, который будет использовать преаллоцированное хранилище для заявок. Но вот реальных примеров этого, вроде как не было.
На остальные места с new/delete (вроде регистрации коопераций и создании подписок) можно бы и забить, т.к. для специфических задач это все будет делаться один раз при старте. Но вот, как минимум, с двумя вышеозначенными моментами, нужно что-то делать.
Плюс еще таймеры... Про которые я сразу и не вспомнил :(
В общем, не думаю, что SO5 пригоден к системам с жестким реальным временем. Насколько можно это учесть в гипотетическом SO6 -- это отдельный вопрос. Но тут более важный вопрос в том, откуда взять средства и ресурсы хотя бы на сбор требований для SO6.
Спасибо за развернутый ответ, Евгений!
Да, без dynamic allocation в C++ жизнь скушна, - без неё никуда.
Поэтому изобретаешь свои велосипеды, но все std контейнеры сразу побоку (абстракция Allocator
не работает хорошо). [На правах рекламы] Благо есть boost::intrusive
, - по-моему, самая правильная идеологически библиотека контейнеров, она и спасает.
Да оно и не касаясь контейнеров закладываться на возможность обеспечения предсказуемой (и кастомизируемой) работы с памятью -- это совсем другой уровень сложности. Вот, например, есть в SO5 возможность ограничить время пребывания в каком-то состоянии:
st_pause.time_limit(75ms, st_normal);
Вроде все просто. А на самом деле при входе в состояние с лимитом создается новый mbox, для него делается подписка, в этот mbox отсылается сообщение. Соответственно, все это может приводить (и приводит к new/delete под капотом).
Если попробовать сделать эту же операцию предсказуемой по расходу памяти (например, чтобы все ресурсы были преаллоцированы на момент создания агента), то лично для меня большой вопрос а можно будет ли написать так же лаконично и понятно. И отдельный большой вопрос, а насколько реализуемо это все будет, с учетом того, что таймер может сработать, сообщение о необходимости выхода из st_pause может встать в очередь, но не успеет обработаться. А агент выйдет из st_pause и зайдет в него вновь. Нам нужно будет где-то взять новое сообщение для таймера, тогда как предыдущее пока еще в очереди ждет.
Я когда-то давно задумывался о том, а насколько просто будет SO5 переделать под задачи, где память должна быть строго под контролем. И пришел к выводу, что придется чуть ли не все перепроектировать. Ну чтобы было по-нормальному, а не просто так получилось.
Не зря же QP/C++ так специфически выглядит и отличается от SO5, CAF и rotor.
SObjectizer: что это, для чего это и почему это выглядит именно так? Взгляд из 2022-го