суббота, 20 июня 2020 г.

[prog.thoughts] Применимость вложенных классов в C++ и серьезность проблемы с forward declaration для них

На RSDN-е угораздило пообщаться с персонажем, насколько уверенном в собственном непогрешимом мнении, что просто караул. К счастью, в Интернетах такие на 100% правые люди встречаются гораздо чаще, чем в реальной жизни. А то работать было бы тяжело.

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

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

понедельник, 15 июня 2020 г.

[prog.flame] Тут опять поднимается волна хайпа вокруг Rust-а, а мне интересно, насколько просто будет на Rust-е...

...делать какие-то вещи из привычной мне окружающей реальности.

Данный пост навеян двумя факторами:

  • после поверхностного знакомства с Rust-ом при написании кода на C++ я зачастую задумываюсь о том, а как такие же вещи можно было бы сделать на Rust-е и насколько бы сложно было бы объяснить Rust-овому компилятору, что я сам понимаю, что делаю;
  • прозвучавшей на днях в этих наших Интернетиках статьей в которой кто-то выступил от имени всего Microsoft-а: "Microsoft: Rust Is the Industry’s ‘Best Chance’ at Safe Systems Programming".

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

Вкратце суть такова: есть класс-родитель, который владеет неким объектом child. При этом у класса-родителя есть метод replace_child:

class child;
using child_unique_ptr = std::unique_ptr<child>;

class parent
{
   child_unique_ptr m_child;

public:
   void replace_child(child_unique_ptr new_child)
   {
      m_child = new_child;
   }
   ...
};

Класс child -- это интерфейс, который имеет несколько реализаций. Что-то вроде:

class child
{
   parent & m_parent;

public:
   ... // some pure virtual methods.
};

class first_stage : public child {...};
class second_stage : public child {...};
class third_stage : public child {...};
...

А фокус в том, что объекты-дети заменяют у родителя сами себя. Т.е. есть код вида:

class first_stage : public child
{
   ...
   void on_some_event() override
   {
      ...
      m_parent.replace_child(std::make_unique<second_stage>(...));
   }
};

Обратить внимание нужно на то, что только parent владеет указателем на child. И вызов replace_child синхронный. Поэтому, когда child вызывает replace_child у своего parent-а, то parent внутри replace_child-а уничтожает того child-а, который и сделал вызов replace_child.

Это ведет к тому, что значение this после возврата из replace_child будет невалидным. И, если в first_stage::on_some_event после возврата из replace_child будут происходить какие-то обращения к this (например, вызовы каких-то нестатических методов и/или чтение/изменение полей объекта), то возникнет UB.

В общем-то, примененное мной решение явно опасное и неоднозначное. Да и вообще программист из меня так себе, поэтому пишу код как умею. Отсюда и такие спорные решения.

Однако, если уж мне не удалось найти более безопасного решения в C++, то программируй я на Rust-е, то и на Rust-е бы я пришел бы к чему-либо подобному. И вот тут как раз и возникает вопрос, который мне любопытен:

А можно было бы в Rust-е применить такой же прием, не прибегая к unsafe?

Мои поверхностные знания Rust-е не позволяют дать точный ответ. Есть смутные подозрения, что без unsafe не обойтись.

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