Мудрецы мира C++ часто напоминают нам о том, как важно максимально точно выражать свои мысли в коде, делать код понятным и простым, не теряя (а зачастую и приобретая) эффективности.
Но подумайте о том, как выглядит код, связанный с динамическим полиморфизмом, в C++.
Сложные иерархии наследования, ручное управление памятью, висячие ссылки, выделение каждому объекту, тот самый виртуальный деструктор, о котором всегда спрашивает Джунс, высокие накладные расходы на вызовы, поскольку компилятор очень плохо справляется с девиртуализацией, классы, которые нельзя использовать повторно - класс, который написан для используемый в полиморфном контексте, его неэффективно использовать в других контекстах, часто создавая ненужные узлы rtti. Типичный код с виртуальными функциями выглядит примерно так:
К сожалению, многие люди просто не замечают здесь ничего особенного; код нормальный? Нет. Суть нашего кода заключается в объектах, которые вызывают разные версии foo, а не в наследовании, указателях или выделении памяти.class Ifoo { public: virtual int foo() const = 0; virtual ~Ifoo() = default; }; class Bar : public Ifoo { public: int foo() const override { return i + 10; } int i; };
Виртуальные функции буквально заставляют нас прятаться в куче шаблонов реализации.
Подумайте, как правильно скопировать такой полиморфный объект. Нам придется создать новый метод, такой как Clone(), стать более сложным, о, это не то, что мы хотели сделать, не так ли? Неужели никто не заметил этой проблемы? Мы, конечно, заметили, но в стандарте C++ долгое время не было других способов работы с динамическим полиморфизмом.
В C++17 появились такие инструменты, как std::any и std::variant; кратко опишем их структуру, плюсы и минусы: std::any — объект этого класса способен хранить любой другой копируемый объект. Вот и все.
Позволяет избежать некоторых выделений, но использовать его практически невозможно, нужно либо догадываться, что там, либо просто использовать как деструктор для любого объекта.
std::variant гораздо интереснее; объект этого класса всегда* хранит объект одного из типов, указанных при компиляции.
Большим плюсом является возможность встраивания деструкторов и других необходимых методов (особенно хорошо для тривиальных типов), никаких аллокаций, НО у этой штуки огромные проблемы с раздуванием кода и временем компиляции, внезапно можно делать ужасные вещи( https://godbolt.org/z/3hxxP8PvW ) к тому же с исключениями еще большие проблемы - вдруг вариант не всегда сохраняет значение, это может быть .
valueless_by_Exception! И я думаю никто серьёзно заниматься этой ситуацией не будет, да и использовать вариант не очень удобно (через std::visit) Подводя итог — std::any ни на что не годится из-за сложности взаимодействия с тем, что лежит внутри, std::variant отлично подходит для тривиальных типов и когда их sizeof близок, но если объекты сложные, они могут выдать исключения или у альтернатив сильно разный sizeof, то это будет крайне неэффективно, не считая, конечно, того, что вам придется менять код каждый раз, когда добавляется новая альтернатива.
И в C++ до сих пор нет стандартного способа справиться с этим! Но не отчаивайтесь, решение есть.
Для начала представьте, каким должно быть идеальное решение, как оно будет выглядеть в коде.
А пока я опишу реальную вещь.
И это очень красиво.
Что, если мы пойдем дальше и расширим идею std::any, позволив нам захватывать произвольные методы объектов? Какая-то концепция времени выполнения? Программист говорит: «Я хочу хранить в этом объекте любые другие объекты, имеющие метод foo», или, строго говоря, удовлетворяющие списку требований.
Пример — программист хочет создать тип, в котором будет храниться любой исполнитель, и он считает исполнителем все, что имеет метод выполнения, принимающий std::function. В идеальном мире в коде я представляю это так: template<typename T>
concept executor = requires (T value, std::function<void()> foo) {
value.execute(foo)
};
using any_executor = any_<executor>;
А теперь реальный код, который действительно работает template<typename T>
struct Execute {
static void do_invoke(const T& self, std::function<void()> foo) {
self.execute(std::move(foo));
}
};
using any_executor = aa::any_with<Execute>;
Все! Тип создан и его можно использовать.
struct real_executor {
void execute(auto f) const {
f();
}
};
int main() {
any_executor exe = real_executor{};
aa::invoke<Execute>(exe, [] { std::cout << "Hello world"; });
}
Более того, конечно, внутри любого_executor можно объявить метод, вызывающий Execute, и использовать класс как все остальные, максимально удобно.
Мы получили четкий и понятный код, семантику значений (копирование, перемещение и прочие вкусности), убрали наследование, управление памятью (и аллокации, по сути, почти все убрали), real_executor можно использовать не только в полиморфном контексте, то есть Повторное использование кода увеличилось, преимущества со всех сторон.
Также важно отметить, что мы получили надежную гарантию исключений, которой не было в этом варианте.
Ну и конечно можно добавлять любое количество требований к типу (методам), можно добавлять только копирование или только перемещение (что, например, устраняет проблему в std::function, для которого добавлен std::move_only_function .
) Я почти забыл - реализация здесь .
Пробуйте, предлагайте, взгляните на C++ по-новому! И обязательно правильно выражайте свои мысли в коде! P.S. Вызов функции по ассемблерному коду идентичен вызову через vtable, Виртуальные функции Аньяни А в остальном использование по бенчмаркам примерно на уровне варианта по производительности (виртуальные функции просто не имеют возможности копировать и перемещать объект, поэтому сравнение было с вариантом и предложенным std::proxy в С++23).
Также в реализации нет ни одного слова virtual и узлы rtti не создаются (но можно подключить метод aa::RTTI и спросить std::type_info у любого).
https://github.com/kelbon/AnyAny Теги: #программирование #C++ #c++17 #anycast #c++20 #any #динамический полиморфизм
-
Высокий Дискурс На Тему Глубокого Обучения
19 Oct, 24 -
Довольны Ли Вы Новинками От Macworld?
19 Oct, 24 -
Какая-То Подозрительная Надпись
19 Oct, 24 -
Укрощение Слона Или Что Такое Хюэ
19 Oct, 24 -
Конкурс «Вышка 10К»
19 Oct, 24 -
Поздравляем Казнета
19 Oct, 24 -
Изменения В Алгоритме Ранжирования Яндекса
19 Oct, 24