В C++ существует такое понятие, как динамическая идентификация типа данных (RTTI). Это механизм, который позволяет определить тип переменной или объекта на этапе выполнения программы. Однако, чтобы уменьшить размер собранных бинарей, во многих проектах RTTI отключают, и от этого перестают работать dynamic_cast и typeid. Но способ проверить, порожден ли инстанс объекта от какого-то базового класса, все же есть, и в своей статье я покажу, как сделать это элегантно.

Отключение RTTI приводит к тому, что для верификации начинают делать разные странные вещи — например, заводят целочисленные class_id и проверяют их вручную, бегая глазами по иерархии классов. Мы же используем магию шаблонов и полиморфизм, чтобы обойтись без этого. Это поможет уменьшить количество бредокода, а также упростит приведение к нужному типу.

Традиционно C++ считается надежным инструментом построения API с проверкой типов еще на этапе компиляции. Исключения составляют шаблоны и ссылки на базовый тип, которые дают относительную свободу в выборе типа. Как правило, когда проект разрастается, стараются экономить каждый мегабайт получающихся бинарников и в первую очередь под нож идет система RTTI.

С одной стороны, конечно, RTTI дает возможность делать dynamic_cast вверх по иерархии наследования, а также узнавать идентификатор типа по typeid, но, с другой стороны, кто и когда этим пользуется? Как правило, dynamic_cast без проблем заменяется static_cast (если библиотека C++ не содержит действительно ветвистое дерево наследования).

Бывает, конечно, что наследование в C++ не сводится к наличию банального базового интерфейса IClassName и наследованию ClassName в недрах библиотеки. Вместо этого может быть представлена полноценная иерархия типов. Тогда без RTTI будет сложно обойтись, просто потому, что мы не сможем взять и проверить тип на инстанцирование определенного типа иерархии через проверку dynamic_cast.

void somefunc(base* b) {
    if (derived* d = dynamic_cast<derived*>(b))

Как правило, либо есть стопроцентная уверенность, что это инстанс определенного класса (которая в половине случаев в итоге оказывается далеко не стопроцентной), либо инстанс типа проверяется на некий уникальный CLASS_ID, переданный при создании экземпляра класса. А это, разумеется, крайне дырявый способ проверить инстанцирование класса, ведь наследники будут иметь свой уникальный CLASS_ID. Поэтому такая проверка возможна не столько на имплементацию класса, сколько на соответствие ровно одному типу. Все это выливается в целые цепочки проверок вида

void somefunc(const creature& c) {
    if (c.class_id() == animal::CLASS_ID) ...
    if (c.class_id() == cow::CLASS_ID) ...

Однако идея, в общем-то, неплоха, давай только сделаем проверку проще и эффективнее. Пусть у нас будет возможность проверить любые два класса иерархии на наследование, а любой инстанс класса этой иерархии — на имплементацию определенного класса.

 

Делаем проверку удобнее

Помогут нам в этом шаблоны и полиморфизм. Если у нас всего два класса, то, кроме шаблона, нам ничего не нужно.

Итак, дано дерево наследования, пусть каждый класс X однозначно идентифицируется по методу X::id(), а также задан typedef для базового класса, поименованный как X::base для каждого класса иерархии.

class B : public A {
public:
    typedef A base;
    static someunique id();

В этом случае шаблон is_class<X>::of<Y>() будет достаточно простым: нужна проверка на соответствие X::id() или непосредственно Y::id(), либо X — один из наследников Y, и, рекурсивно обходя предков, мы найдем соответствие.

template <typename X>
struct is_class {
    template <typename Y>
    static bool of() { return X::id() == Y::id() ||
                       is_class<X::base>::of<Y>(); }

Теперь осталось только обеспечить корректный выход из рекурсии в базовом классе. Для корректного обхода классов иерархии нам нужно, чтобы базовый класс завершал поиск предка.

template <>
struct is_class<root> {
    template <typename Y>
    static bool of() { return root::id() == Y::id(); }

Теперь если есть иерархия creature => animal => cat и animal => dog, то можно спокойно проверять любые классы на наследование друг от друга.

template <typename X>
void meet(X&& x) {
    if (is_class<X>::of<dog>())
        feed(std::forward<dog>(x));

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


Check Also

Исходный кот. Как заставить нейронную сеть ошибиться

Нейросети теперь повсюду, и распознавание объектов на картинках — это одно из самых популя…

Оставить мнение