• “Тринадцатая Дара” Часть 33

  • “Тринадцатая Дара” Часть 16


  • Коротко о динамической типизации в языке C++ на примерах.


    Язык C++, по своей сути, работает строго со статическими типами данных. Но это не мешает ему использовать и динамическую типизацию. Казалось бы парадокс, но практика показывает, что в этом есть смысл.

    Динамическая типизация, на самом деле являеться обёрткой над статической типизацией. Это не только в языке C++, но и во всех других, просто не так откровенно.

    На самом деле, в обычной практике, статической типизации вполне достаточно. Строгость нужна, исключительно ради пользы разработчика, а то ведь можно натворить много всякой жути.

    Использовать динамическую типизацию, есть смысл в редких ситуациях. Часто в примерах упоминают некие гипотетические базы данных. Но я с таковыми не сталкивался. Зато, в моей практике всплыл случай. Честно говоря, можно и тут было обойтись статическими средствами, но я решил, что динамическая типизация будет немного выразительнее.

    Всё начинаеться с компилятора. Для включения особой поддержки rtti включаю её ключом /GR. Я говорю о Microsoft vc++ 10. Это справедливо и для более ранних версий. В прочем динамическая типизация, при некоторых условиях работала и без этой опции. В компиляторе MinGW g++ таковой опции я не нашел.

    rtti можно перевести как: идентификация типов во время выполнения. Механизм заключаеться в добавлении к объекту ещё одного поля. В прочем, если есть виртуальные методы, то rtti может идентефицировать объект по ним.

    У меня есть базовый класс лексемы
    class lexeme
    {
    public:

    int line_num; ///< номер строки
    int char_num; ///< номер символа

    // конструктор
    lexeme()
    {
    this->line_num = 0;
    this->char_num = 0;
    }

    // деструктор
    virtual ~lexeme()
    {
    }
    // получает текст
    virtual string text()
    {
    return( "" );
    }

    // задаёт текст
    virtual void text( string value )
    {
    }

    };

    Это базовый класс, разделяемые указатели на который храняться в списке лексем.

    list< shared_ptr< lexeme > > items;

    В этот список добавляются все лексемы. Но они могут быть разных типов. Для каждого типа лексем, я сделал соответствующий класс.

    // идентификатор
    class lexeme_identificator:
    public lexeme
    {
    public:

    // имя
    string name;

    // деструктор
    virtual ~lexeme_identificator()
    {
    }

    // получает текст
    virtual string text()
    {
    return( this->name );
    }

    // задаёт текст
    virtual void text( string value )
    {
    this->name = value;
    }

    };

    // идентификатор
    class lexeme_endline:
    public lexeme
    {
    public:

    // деструктор
    virtual ~lexeme_endline()
    {
    }

    // получает текст
    virtual string text()
    {
    return( endl );
    }

    };

    И так далее. lexeme_undefined храниться неопределённый символ, а в lexeme_number храниться число. А к примеру в lexeme_operator храниться enum индекс оператора в общей таблице.
    Тоесть каждой лексеме, контейнер для её наиболее удобного хранения.

    Виртуальные методы обеспечивают универсальный доступ к тексту лексемы. Их так задавать, при символьном разборе удобнее. И для быстрой распечатки, тоже в самый раз. Не важно какая лексема храниться в узле.

    // добавление лексемы идентификатора
    shared_ptr< lexeme > lex( new lexeme_identificator() );
    lex->char_num = read_char_num;
    lex->line_num = read_line_num;
    lex->text( read_text.substr( read_char_num, len ) );
    items.push_back( lex );

    Аналогичным способом добавляються и почти все другие лексемы.

    // вывод всех лексем
    list< lexeme >::iterator it;
    for( it = items.begin(); it != items.end(); it++ )
    {
    shared_ptr< lexeme > lex( *it );
    cout << "\"" << lex->text() << "\"." << endl;
    }

    Выводит все лексемы, не обращая внимания на тип.

    А если хочеться заодно и полюбопытствовать типом лексемы при выводе, то можно написать так:

    // вывод всех лексем
    list< lexeme >::iterator it;
    for( it = items.begin(); it != items.end(); it++ )
    {
    shared_ptr< lexeme > lex( *it );
    cout << typeid( *(lex.get()) ).name() << endl;
    cout << "\"" << lex->text() << "\"." << endl;
    }

    Оператор typeid получает идентификатор объекта. Именно объекта! Для этого, пришлось получить и разыменовать указатель.
    Сам идентификатор объекта, являеться объектом класса type_info, который расположен в заголовочном файле typeinfo.
    Метод name() возвращает указатель на строку с терминальным нулём, которая содержит имя типа.

    Мне очень понравилось, как возвращает microsoft vc++ 10. Например вот он выводит лексему

    class plang::lexeme_identificator
    "someclass"

    В тоже время, этот же код, но уже на mingw меня шокировал невнятностью:

    n20lexeme_identificator
    "someclass"

    Ну, что это такое?! Короче, далой его, этот MinGW.
    Весь прикол портит!

    Кроме получения имени, типы можно сравнить. Ну вот хочу я, чтобы при форматном выводе, символы перевода строк, в лог не вставлялись.

    // пропускает лексемы конца строк
    if( typeid( *(lex.get()) ) != typeid( lexeme_endline ) )
    {
    cout << "\"" << lex->text() << "\"." << endl;
    }

    Второй оператор typeid берёт имя класса. Объекты type_info сравниваются. .
    Выражение будет верным, пока не попадётся лексема типа lexeme_endline.

    Я уже выше упоминал о лексеме оператора. Присваивать ей текст, будет означать, что хранить объект будет вынужден не оптимальное представление. Проще всего запихать в объект идентификатор оператора. Но в базовом классе, для этого случая, метод не предусмотрен.

    // добавление лексемы оператора
    shared_ptr< lexeme > lex( new lexeme_operator() );
    lex->char_num = read_char_num;
    lex->line_num = read_line_num;
    dynamic_cast< lexeme_operator* >( lex.get() )->id = operator_id;
    items.push_back( lex );

    Вот так вот. Из указателя на типа базового объекта lexeme*, получили указатель на дочерний lexeme_operator*.
    static_cast и reinterpret_cast откажутся приводить такой указатель.
    В отличии от них, dynamic_cast выполняет приведение, уже во время выполнения программы.
    Во время компиляции, он скушает всякое разное, и не ругнётся.
    А во время выполнения, он проверит допустимость приведения. Если указатель действительно являеться указателем на объект lexeme_operator, то dynamic_cast вернёт указатель правильного типа. В противном случае он вернёт 0.
    В конкретном примере, это теоретически не возможно. Но в случае чего, отработают исключения.

    В других же ситуациях, когда приводиться что попало, то всё же лучше, выполнить проверку возвращенного значения.

    // сравнить операторы
    bool check_operator( shared_ptr< lexeme > value, int operator_id )
    {

    lexeme_operator *plex = dynamic_cast< lexeme_operator* >( *(value.get()) );

    // если вообще не оператор
    if( plex == 0 )
    return( false );

    // сравниваются идентификаторы
    return( plex->id == operator_id );
    }
































































































  • “Тринадцатая Дара” Часть 33

  • “Тринадцатая Дара” Часть 16