Показаны сообщения с ярлыком Внезапно. Показать все сообщения
Показаны сообщения с ярлыком Внезапно. Показать все сообщения

четверг, 14 августа 2025 г.

[prog.c++] dynamic_cast и непубличные базовые классы

Всю свою профессиональную жизнь по мере необходимости пользовался dynamic_cast-ом в C++ и не знал, что dynamic_cast не будет делать приведение к ссылке/указателю на непубличный базовый класс.

Вот пример:

#include <iostream>

class iface {
public:
    virtual ~iface() = default;

    virtual void f() = 0;
};

class basic_impl {
public:
    virtual ~basic_impl() = default;

    void impl_f() {}
};

class first_derived final : public iface, protected basic_impl
{
public:
    void f() override { impl_f(); }
};

class second_derived final : public iface, public basic_impl
{
public:
    void f() override { impl_f(); }
};

void try_dynamic_cast(iface * base)
{
    std::cout << "trying to cast " << typeid(*base).name() << std::flush;
    if(auto * p = dynamic_cast<basic_impl *>(base); p)
        std::cout << "... OK" << std::endl;
    else
        std::cout << "... FAILURE" << std::endl;
}

int main() {
    first_derived f;
    second_derived s;

    try_dynamic_cast(&f);
    try_dynamic_cast(&s);
}

Обращаю внимание, что для first_derived класс basic_impl -- это protected-база.

В результате его запуска получим:

trying to cast 13first_derived... FAILURE
trying to cast 14second_derived... OK

Цынк.

PS. Вот так: век живи, век учись. А помрешь все равно дураком 🙂

понедельник, 9 сентября 2024 г.

[prog.c++] Сюрприз с размером производного класса в разных компиляторах

С размером вот этой простой структуры на x64 при выравнивании по умолчанию все понятно:

struct base {
    void * m_ptr;
    short m_sh;
    char m_ch;
};

Вполне ожидаемо имеем 16 байт. Из которых реально используются 11, а остальное -- это выравнивание на границу 8 байт (т.к. указатель на 64-х битовых платформах занимает 8 байт).

А вот что с размером вот этой не менее простой структуры?

struct derived : public base {
    char m_mask;
};

А вот тут сюрприз!

Результат зависит от компилятора: для VC++ получается 24 байта (и в этом есть некоторая логика), тогда как для GCC и clang -- 16 байт (и в этом также есть логика).

Но лично мне представляется, что логика GCC/clang как-то логичнее. Тогда как поведение VC++ стало неприятным открытием. Можно даже сказать как серпом...

Посмотреть самому можно на godbolt: https://godbolt.org/z/sbo8jPcxE

среда, 24 июня 2020 г.

[prog.c++] Максимальное значение времени, которое можно передать в std::condition_variable::wait_for

Метод std::condition_variable::wait_for в качестве одного из параметров получает время ожидания срабатывания condition_variable. На cppreference.com этот параметр называется rel_time. И про его значение сказано следующее:

an object of type std::chrono::duration representing the maximum time to spend waiting. Note that rel_time must be small enough not to overflow when added to std::chrono::steady_clock::now().

Ok. Значит значение rel_time не должно быть таким, чтобы вызвать переполнение, когда его суммируют с std::chrono::steady_clock::now(). Но каким же оно тогда может быть?

В принципе, можно предположить, что максимально допустимое значение для rel_time может быть получено таким нехитрым способом:

std::chrono::steady_clock::time_point::max() - std::chrono::steady_clock::now()

Т.е. оно не должно быть больше разницы между самой большой меткой времени, которую можно выразить в std::chrono::steady_clock, и текущей меткой времени.

Вроде бы так. Но не все так просто, ведь мы же в мире C++.

Вот программка, которую я написал для того, чтобы проверить, какое значение rel_time можно подсунуть в wait_for и получить при этом ожидаемое поведение wait_for. Ее суть в том, что она пытается брать сперва большое значение rel_time, а затем постепенно уменьшает это значение. Если же значение rel_time слишком большое, то wait_for может завершать свою работу сразу же, вообще без ожидания.

Так вот под Windows и VC++19 (и под FreeBSD и clang) получается вполне себе ожидаемый результат:

wait_time: 9222236437359ms, days: 1
wait for: 1001ms

А вот под Linux-ом и GCC/clang меня ждет неожиданное открытие:

wait_time: 9223171751593ms, days: 1
wait for: 0ms
wait_time: 9136771751593ms, days: 1001
wait for: 0ms
wait_time: 9050371751593ms, days: 2001
wait for: 0ms
wait_time: 8963971751593ms, days: 3001
wait for: 0ms
wait_time: 8877571751593ms, days: 4001
wait for: 0ms
wait_time: 8791171751593ms, days: 5001
wait for: 0ms
... skipped
wait_time: 7840771751592ms, days: 16001
wait for: 0ms
wait_time: 7754371751592ms, days: 17001
wait for: 0ms
wait_time: 7667971751592ms, days: 18001
wait for: 0ms
wait_time: 7581571751592ms, days: 19001
wait for: 1000ms

Т.е. мало того, что нельзя просто взять разность между (time_point::max() - time_point::now()) и для безопасности отнять оттуда какую-то разумную дельту (вроде 24 часов). Эту самую дельту нужно сделать весьма большой.

Почему так -- я хз. Вероятно какие-то фокусы трансформации вызова condition_variable::wait_for в POSIX-овый вызов. Но вот на практике довелось в это стукнуться.

PS. Зачем может потребоваться задавать большие значения для rel_time? Бывают случаи, когда либо пользователь задает время ожидания какого-то события, либо же нужно ждать "когда рак на горе свиснет". И если мы хотим время ожидания представить в программе всего одним значением std::chrono::duration, которое можно будет просто без дополнительных проверок отдать в condition_variable::wait_for (или не делать отдельно вызовы condition_variable::wait и condition_variable::wait_for), то нужно понимать, какая именно величина будет означать "когда рак на горе свиснет".

воскресенье, 16 февраля 2020 г.

[life.cinema] Как бы я попробовал бы сделать продолжение Терминатор-2

При написании последнего кинообзора не хватило времени и желания развить мысль по поводу безнадежно угробленной франшизы "Терминатора". А хотелось бы высказать и свою идею о том, как можно было бы попробовать сделать прямое продолжение "Терминатор-2", отбросив снятые ранее третью, четвертую и пятые части. Попробую сделать это сегодня. При этом хочу предупредить, что описанная ниже идея посетила меня еще до того, как мне стали известны общие детали сюжета "Терминатор: Темные судьбы".

Итак, идея в том, что в рамках рассказанной в первых двух фильмах истории Skynet отправил в прошлое не двух T-800, а трех. Второго из них уничтожили в первом "Терминаторе", третий стал главным героем "Терминатор-2". А вот первый был отправлен Skynet-ом в прошлое еще раньше, еще до действий первого фильма. Его задачей было отследить происхождение Джона Коннора.

Нас же не удивляет, что в первом фильме терминатор из будущего прибыл именно в Лос-Анджелес и не удивляет, что он начал разыскивать Сару Коннор, хотя точно не знал какую именно. Но откуда у Skynet-а появилась информация о Саре Коннор из Лос-Анджелеса?

вторник, 3 декабря 2019 г.

[prog.c++] Похоже, что втоптался в неприятную особенность static constexpr членов класса

Почему-то пребывал в уверенности, что если в C++ объявлять статические члены класса как static constexpr, то компилятор с линкером автоматически будут разруливать ситуации с множественными определениями таких статических членов в разных единицах трансляции. Но не так все просто оказалось.

Дабы не быть голословным создал просто демо-пример.

Тут есть заголовочный файл qvalue.hpp, внутри которого объявляется класс со static constexpr полем maximum.

Есть два файла (a.cpp и b.cpp), которые внутри себя загружают qvalue.hpp и используют qvalue_t::maximum. Эти файлы компилируются по отдельности и результаты их компиляции затем собираются в один статический файл.

Так вот, если компилировать более-менее современными версиями GCC и clang (скажем, начиная от GCC-5.5 и clang-6.0) с оптимизацией, т.е.:

$ g++ -std=c++14 a.cpp b.cpp main.cpp -O2

то никаких проблем нет, все успешно компилируется и линкуется.

Но вот если собирать без оптимизации, т.е.:

$ g++ -std=c++14 a.cpp b.cpp main.cpp -O0

то возникает ошибка линковки:

/tmp/ccHv44S3.o: In function `a(unsigned int&)':
a.cpp:(.text+0x1e): undefined reference to `qvalue_t::maximum'
/tmp/ccIQACTW.o: In function `b(unsigned int&)':
b.cpp:(.text+0x1e): undefined reference to `qvalue_t::maximum'
collect2: error: ld returned 1 exit status

При этом в VC++ проблем нет.

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

пятница, 18 октября 2019 г.

[prog.c++] Почему C++ никому не нужен, но никуда не денется, на одном маленьком примере

Из свеженького:

template<
   template<classclass Container_Adaptor >
struct to_container_consumer_t : public consumer_tag
{
   templatetypename Container, typename Item >
   void
   consume( Container & to, Item && item )
   {
      Container_Adaptor<Container>::store( to, std::move(item) );
   }
};

Это шаблонный класс, который параметризуется другим шаблонным классом. И у которого всего один шаблонный метод, который параметризуется еще двумя типами.

Задача объекта этого шаблонного класса -- взять некое значение (не суть важно какое) и переместить его в некий контейнер (не суть важно какой). Как именно будет происходить перемещение to_container_consumer_t не знает. Он лишь знает, что это перемещение можно сделать посредством шаблонного параметра Container_Adaptor.

Насколько я понимаю (а понимаю не так, чтобы уж много), to_container_consumer_t -- это метафункция, которая параметризуется другой метафункцией. И в C++ это вот выражается вот таким вот образом.

Так вот, C++ не нужен в мейнстриме именно потому, что в нем можно выражать метафункции, параметризуемые другими метафункциями. А в мейнтриме это никому не нужно. Мягко говоря.

И C++ никуда не денется именно потому, что в нем можно выражать метафункции, параметризуемые другими метафункциями. Это в принципе мало кому нужно. Но, в принципе, нужно. И вот те, кому нужно, и будут продолжать использовать C++.

За последние 3-4 недели довелось поучаствовать в нескольких спорах на тему применимости C++ и востребованности/оправданности новых возможностей C++. И прочитать еще несколько таких споров. И вот что я думаю: C++ должен превратиться во что-то типа Хаскелля. Многие слышали о нем. Многие думают, что это круто и с его помощью можно творить разные крутые вещи. Многие считают, что это не язык для простых смертных. Но мало кто имеет реальное представление о нем. Еще меньше имеют возможность его применять. И еще меньше им владеют.

И, вообще-то говоря, превращение C++ во что-то типа Хаскелля с постепенным отходом от мейнстрима, пойдет на пользу всем. И C++. И тем, кто из него уйдет. И тем, кто в нем останется.

Так думаю на данный момент.

Под катом маленький пример того, как этот to_container_consumer_t используется. И несколько реализаций Container_Adaptor.

среда, 16 октября 2019 г.

[prog.c++] Наткнулся на неожиданную для себя особенность объявления static constexpr в классе

Столкнулся вчера с ситуацией, в которой не компилировался код, казавшийся мне нормальным. Вот минимальный воспроизводимый пример:

#include <cstdint>

class vholder_t
{
public :
   using underlying_type_t = std::uint_least16_t;

   class trusted
   {
      const underlying_type_t m_value;

   public:
      explicit constexpr
      trusted( underlying_type_t v ) noexcept : m_value{v} {}

      constexpr auto
      get() const noexcept { return m_value; };
   };

   static constexpr trusted max{1000u};

private :
   underlying_type_t m_value;

public :
   vholder_t( trusted v ) noexcept : m_value{ v.get() }
   {}
};

int main()
{
   using trusted = vholder_t::trusted;

   constexpr trusted max{1000u};

   vholder_t v{ trusted{200u} };

   return 0;
}

Ошибка возникала в строчке:

static constexpr trusted max{1000u};

Диагностика у разных компиляторов была разной, в основном все говорили о невозможности использовать trusted в constexpr контексте. Кто-то ругался на неопределенные конструкторы в trusted, кто-то на то, что max должен инициализироваться константой.

Собственно, на эту красоту можно полюбоваться на godbolt-е: https://gcc.godbolt.org/z/lP0XlT.

Самой полезной оказалась диагностика от GCC-8 (и GCC-9). Оказалось, что компилятор почему-то считает, что в месте объявления max тип trusted еще не определен, поэтому trusted и не может быть задействован в constexpr контексте.

Поэтому был применен следующий workaround: