Russian Qt Forum

Программирование => С/C++ => Тема начата: ViTech от Июль 02, 2018, 10:44



Название: Unified Pointer Library
Отправлено: ViTech от Июль 02, 2018, 10:44
Делаю такую штуку. Может ещё кому пригодится.

Unified Pointer Library (https://gitlab.com/UnifiedPointers/Library/UPL) (описание на русском (https://gitlab.com/UnifiedPointers/Library/UPL/tree/master/doc/ru)) - библиотека унифицированных указателей (UPL), содержит концепты и реализации умных указателей, которые предназначены для управления временем жизни объектов. Предоставляет указатели upl::unique и upl::shared для уникального и совместного владения объектами, слабые ссылки для них upl::weak, и добавляет унифицированный тип владения upl::unified. Публичный интерфейс унифицированных указателей схож с интерфейсом умных указателей стандартной библиотеки C++. Библиотека header-only, лицензия MIT.

Ключевые особенности:
  • Возможность организации ассоциативных связей между объектами в соответствии с UML.
  • Определены концепты, которые в compile-time позволяют гибко определять тип указателей UPL в обобщённых алгоритмах.
  • Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
  • Указатель upl::unified позволяет передать уникальное владение объектом в цепочке, где может выполняться копирование.
  • С помощью upl::unified можно временно продлить время жизни объекта в заданной области видимости, что позволяет корректно завершить работу с ним, даже когда все остальные указатели на этот объект удалены.
  • Добавлены указатели с одинарной кратностью, которые не могут быть пустыми и всегда ссылаются на один объект.

Отвечу на возникающие вопросы. Аргументированная критика приветствуется :).


Название: Re: Unified Pointer Library
Отправлено: vipet от Июль 02, 2018, 13:53

Отвечу на возникающие вопросы.

в каких случаях и чем лучше stl?


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 02, 2018, 14:27
в каких случаях и чем лучше stl?

Лучше в случаях работы в многопоточной среде. В частности, можно создать слабую ссылку/связь upl::weak на объект, находящийся под уникальным владением upl::unique. Далее, через upl::weak можно безопасно получить доступ к объекту. Тут возможны два варианта:
1. Объект удалился. upl::weak::expired() == true, объекта нет, обращаться не к чему.
2. На момент доступа через upl::weak объект существует, но внезапно может удалиться в другом потоке. upl::weak::lock() продлевает время жизни объекта до конца работы текущей области видимости.

Если коротко, то добавлена связка upl::unique <-> upl::weak, аналогичная std::shared_ptr <-> std::weak_ptr, только для уникального владения. Если подробнее, то в библиотеке набор указателей, каким я бы хотел его видеть. С нормальным уникальным владением, с унифицированным доступом, с различной кратностью, с поддержкой концептов в шаблонах и прочим.


Название: Re: Unified Pointer Library
Отправлено: Пантер от Июль 02, 2018, 14:34
А что с производительностью? ;)


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 02, 2018, 14:44
А что с производительностью? ;)

Это от реализации зависит :). Кстати, можно написать свою, под свои нужды. Текущая реализация наивная, и ещё до конца не доделанная, ибо не хочется велосипед изобретать. Она нужна для проверки концепции. Сейчас делаю реализацию с более узким спектром возможностей, но на готовых std::shared_ptr/std::weak_ptr. В ней производительность будет такой же, как у std::shared_ptr.


Название: Re: Unified Pointer Library
Отправлено: ssoft от Июль 03, 2018, 10:38
Вопросов очень много). Пока по крупному

1. Предполагается альтернативная реализация указателям std или это принципиально новый набор умных указателей?

Какие преимущества? Почему для уникального владения нельзя взять и использовать std::shared_ptr, пусть даже контроль уникального использования ложится на программиста.
Можно же реализовать уникальное владение через using unique = shared_ptr; using weak = weak_ptr; using unified = shared_ptr;
Не возникнет ли трудностей у программиста в выборе "подходящих" указателей из большого числа вариантов?

2. В реализации используется куча счетчиков (куда уж тет без них))). В параллельном доступе std::unique_ptr будет эффективнее, чем указатели со счетчиками, а raw pointer еще чуть эффективнее (зависит от компилятора).

Так ли уж нужно жертвовать производительностью?
Или предполагается различная реализация - со счетчиками/без счетчиков, атомарных/неатомарных и др., со всяческими сочетаниями этих свойств.
В этом случае комбинаторное сочетание свойств огромно и более того нецелесообразно (есть альтернативный подход).

3. В примере используется тэг in_place. Однако экземпляр объекта string формируется в куче.
Не предполагает ли in_place формирование экземпляра непосредственно на стеке? И как быть тогда со всеми слабыми указателями и продлением жизни?

4. Почему для реализации ассоциативных связей выбраны умные указатели, а не умные ссылки, например)?

Удобно ли использовать повсеместно указатели? Внешний вид указателя ничего не говорит о том, что это - атрибут или ассоциативная связь. В результате - одинаковая реализация, но разные свойства и смысловая нагрузка.
Могу попробовать показать, что концепция ссылок более "дружелюбная" нежели указателей.

Другими словами можно написать так

Код
C++ (Qt)
void foo ( const Value & value );
 

или так

Код
C++ (Qt)
void foo ( Value const * const value );
 

Оба варианта будут работать, но первый выглядит на мой взгляд лучше.


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 03, 2018, 12:35
Вопросов очень много). Пока по крупному

1. Предполагается альтернативная реализация указателям std или это принципиально новый набор умных указателей?

В идеале хотелось бы получить интеграцию с указателями std, чтобы, во-первых, изобретать велосипеды по минимуму, во-вторых была возможность взаимодействовать с кодом, ориентированным на std. Но получается так, что даже если сделать реализацию UPL на основе std::shared_ptr/weak_ptr, то совместимы будут только std::shared_ptr и upl::shared (возможно их взаимное преобразование друг в друга). std::unique_ptr и std::weak_ptr можно только в одну сторону перевести - в upl::unique, upl::weak, обратно не получится (хотя в случае upl::unique -> std::unique_ptr есть варианты, но довольно зыбкие). Так что, в зависимости от реализации UPL, это может быть частичная интеграция с указателями std, а может быть их альтернатива.

Какие преимущества? Почему для уникального владения нельзя взять и использовать std::shared_ptr, пусть даже контроль уникального использования ложится на программиста.
Можно же реализовать уникальное владение через using unique = shared_ptr; using weak = weak_ptr; using unified = shared_ptr;

Для уникального владения можно взять и использовать std::shared_ptr (я так понял, что многие так и делают). Но моё личное мнение такое, что это то же самое, как предложить не пользоваться ключевыми словами const, explicit, protected, private, delete и т п. Писать программы станет проще, не будет надоедливых ошибок компилятора, можно меньше ломать голову const-или-не-const. Станет ли программа от этого лучше? Сомневаюсь. Ещё такой момент: типов ассоциативных связей всего три. Три, Карл!!! :) И тут говорят: выкидывайте из модели все композиции, потому что на великом и могучем С++ нормальное(потокобезопасное) уникальное владение сделать нельзя/трудно/не охота/а зачем, и мы это на std::shared_ptr будем делать. Для меня это нездоровая ситуация.

Не возникнет ли трудностей у программиста в выборе "подходящих" указателей из большого числа вариантов?

Программисты разные бывают, кто не хочет соблюдать const correctness, тот и о типе владения не будет особо задумываться, влепит std::shared_ptr  и всё. Выбор подходящих указателей нужен тем, кто хочет иметь полный набор возможностей, кто пишет классы по модели UML, и хочет защитить их от неправильного использования. Например, как с помощью explicit защищают от случайного преобразования типов.

2. В реализации используется куча счетчиков (куда уж тет без них))). В параллельном доступе std::unique_ptr будет эффективнее, чем указатели со счетчиками, а raw pointer еще чуть эффективнее (зависит от компилятора).

Так ли уж нужно жертвовать производительностью?

Если в программе получается организовать связи между объектами и доступ к ним с помощью std::unique_ptr и raw pointers, и исключены появления dangling pointers, то так и следует делать. Но в более-менее сложной программе не факт, что получится соблюдать целостность связей проще и эффективнее, чем подсчётом ссылок. upl::unique не является заменой std::unique_ptr, у них одинаковая семантика владения, но немного разное поведение при уничтожении объекта. UPL ориентирована на многопоточное использование, где объекты могут внезапно умирать.

Или предполагается различная реализация - со счетчиками/без счетчиков, атомарных/неатомарных и др., со всяческими сочетаниями этих свойств.
В этом случае комбинаторное сочетание свойств огромно и более того нецелесообразно (есть альтернативный подход).

Ситуация с реализациями такая: задумка UPL в том, чтобы сделать работу с указателями более упорядоченной и систематизированной. Можно провести аналогию со стандартными контейнерами. Есть концепты контейнеров (https://en.cppreference.com/w/cpp/named_req/Container), есть стандартные алгоритмы (https://en.cppreference.com/w/cpp/algorithm) для работы с ними, и есть конкретные реализации контейнеров (https://en.cppreference.com/w/cpp/container). В UPL я попытался определить типы владений (https://gitlab.com/UnifiedPointers/Library/UPL/blob/master/doc/ru/TheoreticalBasis.md#%D0%B2%D0%BB%D0%B0%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5) и составить набор концептов указателей (https://gitlab.com/UnifiedPointers/Library/UPL/blob/master/doc/ru/Reference.md#%D0%BA%D0%BE%D0%BD%D1%86%D0%B5%D0%BF%D1%82%D1%8B-%D1%83%D0%BA%D0%B0%D0%B7%D0%B0%D1%82%D0%B5%D0%BB%D0%B5%D0%B9). Эти концепты можно использовать в обобщённых алгоритмах, чтобы не было привязок к каким-то конкретным типам указателей (std::shared_ptr, upl::shared). Соответственно, реализаций указателей может быть несколько, равно как можно написать свою версию std::vector, например. Будет ли кто писать свои реализации? Маловероятно ). Но такая возможность есть. В самой же UPL будет какая-то реализация, которая подойдёт для подавляющего большинства случаев. Скорей всего это будет реализация на основе std::shared_ptr/weak_ptr.

3. В примере используется тэг in_place. Однако экземпляр объекта string формируется в куче.
Не предполагает ли in_place формирование экземпляра непосредственно на стеке? И как быть тогда со всеми слабыми указателями и продлением жизни?

Тег std::in_place (https://en.cppreference.com/w/cpp/utility/in_place) используется для "пробы пера": насколько удобно пользоваться такой формой создания/инициализации объекта, в отличие от new SomeObject(), make_unique<SomeObject>(),  make_shared<SomeObject>(). Обозначает: создание объекта в этом месте (программы, а не стек/куча) средствами самого класса. Для объекта на стеке свои правила времени жизни, там ссылок достаточно, вряд ли надо делать указатели настолько универсальными.

4. Почему для реализации ассоциативных связей выбраны умные указатели, а не умные ссылки, например)?

Удобно ли использовать повсеместно указатели? Внешний вид указателя ничего не говорит о том, что это - атрибут или ассоциативная связь. В результате - одинаковая реализация, но разные свойства и смысловая нагрузка.
Могу попробовать показать, что концепция ссылок более "дружелюбная" нежели указателей.

Другими словами можно написать так

Код
C++ (Qt)
void foo ( const Value & value );
 

или так

Код
C++ (Qt)
void foo ( Value const * const value );
 

Оба варианта будут работать, но первый выглядит на мой взгляд лучше.

Вообще, UPL - это побочный продукт более крупного проекта CppUml (https://gitlab.com/CppUml) ). Просто получилось так, что небольшую часть можно выделить для стороннего использования, без зависимостей и заумных терминов UML. Для тех, кому привычнее семантика указателей и не нужно ничего лишнего. Реализацию указателей UPL можно отдельно протестировать и обкатать. И эту реализацию можно использовать в токенах UML, которые больше похожи на value-семантику. И в CppUml можно больше вводить терминов и сущностей, для тех кому это действительно надо. Но это уже другая история :).


Название: Re: Unified Pointer Library
Отправлено: Igors от Июль 03, 2018, 13:09
Какие преимущества? Почему для уникального владения нельзя взять и использовать std::shared_ptr, пусть даже контроль уникального использования ложится на программиста.
Можно, но хреновато выходит - совершенно теряется смысл shared, то ли он действительно шарится, то ли чтобы на него навесить weak, то ли просто "сам удалится". 

А как насчет интрузивного использования? Вот у меня масса древнего кода с "голыми" указателями. Бездумно делать их вумными (теми или иными) - ну во-первых очень непросто при больших объемах кода, а главное - я совершенно не уверен что это правильно и решит все проблемы, вероятно наоборот, создаст новые. Однако проверить валидность указателя очень хотелось бы. Интересует простейшая ситуация: указатель, напр член класса, не отвечает ни за создание ни за удаление, он просто "использует" объект на который указатель указывает - и все




Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 03, 2018, 13:55
А как насчет интрузивного использования? Вот у меня масса древнего кода с "голыми" указателями. Бездумно делать их вумными (теми или иными) - ну во-первых очень непросто при больших объемах кода, а главное - я совершенно не уверен что это правильно и решит все проблемы, вероятно наоборот, создаст новые. Однако проверить валидность указателя очень хотелось бы.

Здесь та самая проблема: :)
Не возникнет ли трудностей у программиста в выборе "подходящих" указателей из большого числа вариантов?

Когда были только "голые" указатели, то выбора не было и думать особо не надо было. О владении вспоминали только когда память текла, и решали, кто таки должен удалять объект. И как это обозначить. При этом, под тот код можно было построить UML модель, хоть разработчик классов об этом и не подозревал :). С появлением умных указателей в С++11 уже появился выбор. Хотя, похоже, он сводился к shared_ptr или не shared_ptr?

С древним кодом сложно. Сначала надо выявлять, кто владелец, а кто просто посмотреть зашёл. Затем, по-хорошему, тип владения (а по-простому - std::shared_ptr). Если есть вероятность появления "висячих" указателей, и необходимо иметь возможность проверить валидность указателя, то без накладных расходов тут вряд ли обойдётся. В этом отношении std::unique_ptr вряд ли что-то адекватное может предложить. При использовании умных указателей изменится способ доступа к объектам. В частности для std::weak_ptr надо lock() делать. Так что работы много, и не факт, что всё хорошо закончится.

Интересует простейшая ситуация: указатель, напр член класса, не отвечает ни за создание ни за удаление, он просто "использует" объект на который указатель указывает - и все.

В этом случае стандарт С++ может предложить std::observer_ptr (https://en.cppreference.com/w/cpp/experimental/observer_ptr). Но года через 2, в С++20 :). Хотя, похоже, это простейшая обёртка над raw pointer и проверить "живучесть" объекта через него вряд ли получится. Можно написать свой.

Ещё ведутся работы над Profiles (https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#pro-profiles) с Bounds safety и Lifetime safety, наверное что-то похожее на то, что в Rust есть. Но когда это будет нормально сделано (и будет ли сделано вообще) - не известно.

Так что спасение утопающих - дело рук самих утопающих :). И использовать стороннюю библиотеку, или обходиться стандартными инструментами - это, как обычно, на усмотрение разработчиков проекта.


Название: Re: Unified Pointer Library
Отправлено: ssoft от Июль 04, 2018, 08:50
И все-таки)

Какова предполагаемая аудитория UPL? Что меня, как разработчика, должно подвигнуть к использованию этих указателей взамен стандартных или вместе с ними?

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

Для понимания всех тонкостей требуется достаточно высокий порог вхождения, как следствие - низкий интерес к данной тематике.
Мы обсуждали с ViTech подобные вопросы с начала 2016 года на внутренней площадке, но никто так и не присоединился к какому-либо серьезному обсуждению.

ViTech, там у нас много интересных материалов накопилось, их можно публиковать при необходимости, если нет возражений. Их обсуждение позволило мне, по крайне мере, восполнить многие пробелы в понимании, что есть объектно ориентированное моделирование). Спасибо.

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

Я не говорю, что это "плохие" разработчики. Нет! Они пишут рабочий код, бывает даже отличного качества.) Но для них понятия из разряда none/shared/composite + "продление жизни" чужды и сложны для восприятия.

А для тех, кто понимает о чем идет речь, понятно как это реализуется посредством стандартных инструментов. Они хорошо знают, что "unique + продление жизни" реализуется посредством shared механизма, и что COW (copy on write) с уникальным владением также реализуется посредством shared механизма. Они не видят смысла подтягивать достаточно сложный в понимании инструмент, для решения задач, которые решаются и стандартными средствами. Пусть с помощью стандартных средств получается немного сложнее, зато понятно всем.

Нужно предложить нечто большее.

Я много раз спрашивал - почему указатели? Это достаточно низкоуровневые типы - инструментальные кирпичики языка программирования, с помощью которых можно реализовать все что угодно.
В UML нигде не вводится такого понятия, как Pointer. Слово "указатель" встречается в тексте всего 1 раз, в качестве примера технической реализации Link. Понятия объектной модели могут быть реализованы и совсем без указателей.
При использовании указателей в примере ниже в зависимости от типа Type потребуется модификация кода, но смысловая нагрузка m_attribute при этом не меняется.

Код
C++ (Qt)
struct MyStruct
{
   //using Type = std::string;
   //using Type = std::unique_prt< std::string >;
   Type m_attribute;
};
 
void foo ( const std::string & value );
 
...
foo( my_struct.m_attribute ); //foo( *my_struct.m_attribute );
...
 

Так почему же указатели? )))

На самом деле мне удалось заинтересовать людей данной темой, правда немного в другом контексте. Опишу его в следующих постах), а также актуализирую и перенесу проект на gitlab.


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 04, 2018, 13:14
А для тех, кто понимает о чем идет речь, понятно как это реализуется посредством стандартных инструментов. Они хорошо знают, что "unique + продление жизни" реализуется посредством shared механизма, и что COW (copy on write) с уникальным владением также реализуется посредством shared механизма. Они не видят смысла подтягивать достаточно сложный в понимании инструмент, для решения задач, которые решаются и стандартными средствами. Пусть с помощью стандартных средств получается немного сложнее, зато понятно всем.

В сухом остатке скорей всего происходит то, что выделено жирным :). Никто же не поругает за то, что композицию из модели UML реализовали через shared_ptr. Если вообще кто-то задумается о какой-то там модели. Кто-то может задумается, но успокоит себя мыслью: "на самом деле там же совместное владение, в методе/функции происходит же захват владения, зачем тогда уникальным прикидываться?". Кто-то попробует использовать unique_ptr, споткнётся пару раз, плюнет, и перейдёт на shared_ptr.

С уникальным владением сложнее обращаться, потому что оно накладывает ограничения. Например я столкнулся с таким: хочется создать очередь команд из std::queue<std::function<void()>>, но нельзя сделать std::function<void()> = std::bind(&SomeObject::someMethod, object, std::move(unique_arg));, потому что std::function - CopyConstructible (пример TransferUnique (https://gitlab.com/UnifiedPointers/Example/TransferUnique/blob/master/src/main.cpp#L158)). Приходится какой-то костыль лепить.

Ещё пример. Есть Guidelines Support Library  (https://github.com/Microsoft/GSL), в ней делают not_null (https://github.com/Microsoft/GSL/blob/master/include/gsl/pointers#L68) (Restricts a pointer or smart pointer to only hold non-null values). Но этот not_null не работает с unique_ptr (https://github.com/Microsoft/GSL/issues/89), уже почти 3 года прошло :). И всем пофиг (почти (https://github.com/Microsoft/GSL/pull/675)).

Далее. Igors затронул интересную тему. С голыми указателями писать программу проще, не надо задумываться о типах владения и прочем (потом может боком вылезти, но кто об этом задумывается). В параметрах методов/функций тоже всё проще: обычно какой-нибудь SomeObject *. Если же делать по уму и использовать разные типы владения, то в параметрах будет больше разнообразия: unique_ptr, shared_ptr, weak_ptr (и SomeObject * тоже могут присутствовать). С таким многообразием параметров очень скоро программу писать станет не интересно, и везде останутся одни лишь shared_ptr. Я подозреваю, что сейчас так и происходит.

Где-то может использоваться внутреннее управление временем жизни объектов, и другое уже не прилепишь. Тот же QObject.

Из таких предпосылок я делаю вывод, что просто никто не хочет связываться с уникальным владением, раз можно обойтись совместным и никто за это не осудит. Ну а "модель UML", "типы владения" и пр. - это просто слова, которые важны каким-нибудь перфекционистам. Мне например ). А так как есть shared_ptr и его в общем-то хватает, то зачем ещё какие-то библиотеки таскать, тем более от какого-то неизвестного ViTech ). Вот если б Herb Sutter такое предложил, то да )).

Нужно предложить нечто большее.

Нечто большее может как раз лежать в области унифицированного доступа к объектам. Грубо говоря, вместо зоопарка uniqe, shared, weak в параметрах, можно использовать один upl::unified. Потому что вся эта шелуха с указателями/ссылками возникает из-за ограниченности языков программирования/аппаратных платформ, а на деле нужны сами объекты. Чтобы очистить объект от этой шелухи и добраться до него приходится много неинтересного кода писать. Вот сущности типа upl::unified и концепты (пример (https://gitlab.com/UnifiedPointers/Example/ConceptPrinter/blob/master/src/main.cpp)) могут в этом помочь. Тогда и уникальное владение может будет проще использовать и оно получит распространение.

Я много раз спрашивал - почему указатели? Это достаточно низкоуровневые типы - инструментальные кирпичики языка программирования, с помощью которых можно реализовать все что угодно.
В UML нигде не вводится такого понятия, как Pointer. Слово "указатель" встречается в тексте всего 1 раз, в качестве примера технической реализации Link. Понятия объектной модели могут быть реализованы и совсем без указателей.
При использовании указателей в примере ниже в зависимости от типа Type потребуется модификация кода, но смысловая нагрузка m_attribute при этом не меняется.

Так почему же указатели? )))

В UML про указатели не говорят, зато много говорят о токенах объектов. Если коротко, то токены и указатели по сути одно и то же (умные указатели, а не голые с арифметикой). Я бы даже сказал так: токен - это урезанный указатель. По крайней мере с технической точки зрения (реализации ) у меня так получается. И все сущности более высокого уровня (свойства, параметры, переменные) строятся на основе токенов. И доступ к объекту через токен, скорей всего, будет выглядеть как *token. Тут мог бы помочь operator dot, но я что-то не вижу подвижек по нему, такое ощущение, что он заглох.

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

Тем же, кому нужны более высокоуровневые сущности: свойства, параметры, переменные, которые являются коллекциями по трактовке UML, предлагается другая библиотека, со всеми этими наворотами. В основе которой я вижу токены, коллекции, и Ranges (https://github.com/ericniebler/range-v3). Если появятся желающие это обсудить - я будут только рад :).


Название: Re: Unified Pointer Library
Отправлено: Igors от Июль 09, 2018, 06:30
Ну вот, и полемика (обещавшая быть такой интересной) почему-то быстро затухла  :)  Здесь, конечно, всегда есть проблема (или особенность) "общих" вещей - заниматься ими весьма приятно, а ответственности практически никакой, всегда можно сказать типа: "да мало ли что тебе это не нужно - а другому может очень нужно!".

Ладно, попробуем оживить. Вот напр типичное место старого кода которое давным-давно надо бы переделать
Код
C++ (Qt)
struct Light {
 ...
 Light * masterLight;  // "голый" указатель на мастера
 
// пример использования мастера
 float GetIntesity( void ) const
 {
   if (masterLight && this->inheritIntensity())    // возвращаем значение мастера
     return masterLight->GetIntensity();
   else
     retur this->m_intensity;  // или собственное
 }
};
В чем недостатки такой конструкции и как ее улучшить?


Название: Re: Unified Pointer Library
Отправлено: ssoft от Июль 09, 2018, 08:52
Ну вот, и полемика (обещавшая быть такой интересной) почему-то быстро затухла  :) 

Мы с ViTech, как всегда, перешли в приват))).

В чем недостатки такой конструкции и как ее улучшить?

Это с какой стороны посмотреть)). Имхо, прописной истины как таковой нет, зато куча ЕСЛИ.

С точки зрения программы на C++ здесь все нормально.

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

С точки зрения поддержки, фиксов и т.п. Нужно применить некоторые усилия, чтобы понять что есть что. Указатель сам по себе ничего не говорит о том, чем является и какими свойствами должен обладать.
Что это Light * ???? - значение, динамический массив, агрегация, композиция, какая предполагается мультипликативность и т.д. и т.п. Какие свойства присущи Light * masterLight ?
Чтобы выяснить это, требуется проанализировать сам код, а при поиске ошибок еще и найти места некорректного использования.

В этом могла бы помочь объектно-ориентированная модель, как абстракция более высокого уровня. Но для этого её нужно проектировать)). Или ... реализовать конструкции, которые явно отражают понятия модели в программе.
Сразу качественный скачок от кодирования к моделированию сложен (как от 2D перейти к 3D, или от 3D к 4D), поэтому UPL предлагает ряд более простых инструментов в виде дополнительных умных указателей.

Здесь я схитрю)), этот код не на UPL.

Код
C++ (Qt)
struct Light {
 ...
   using WeakMaster = AssociationEnd< Light, AggregationKind::None >;
   WeakMaster masterLight;  // полюс вида None ассоциативной связи агрегации c мастером
 
// пример использования мастера
   float GetIntesity( void ) const
   {
       // гарант доступа к мастеру на протяжении всего вызова метода
       AccessGuard<WeakMaster > master_access_guard = masterLight;
       if ( master_access_guard && this->inheritIntensity())    // возвращаем значение мастера
           return master_access_guard->GetIntensity();
       else
           return this->m_intensity;  // или собственное
   }
};
 

Из кода явно видно, что есть что. А наличие гаранта в методе позволяет обеспечить при необходимости "продление жизни" masterLight и/или однопоточный доступ к masterLight и/или ...


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 09, 2018, 11:33
Ладно, попробуем оживить. Вот напр типичное место старого кода которое давным-давно надо бы переделать
...
В чем недостатки такой конструкции и как ее улучшить?

ssoft всё правильно написал. Такая конструкция вполне имеет право на существование. Со множеством ЕСЛИ :). Например, такая конструкция может легко и в одном потоке сломаться. Тот, кто удаляет masterLight, сначала должен удалить все связи с ним из других Light. По идее, это должен делать тот же, кто эти связи устанавливал. Если есть такие гарантии отсутствия "висячих" указателей, то можно и без умных указателей обойтись. Вопрос в том, что проще: такое ручное управление связями, или использование других средств определения "живучести" объекта? Чтобы можно было "на месте" узнать, жив объект или уже помер. Например, такая функциональность есть у QPointer. Но он только для QObject и не является потокобезопасным. А в многопоточности всё намного веселее.

Решить проблему "висячих" указателей можно с помощью умных указателей std. Чтобы совсем не ломать голову, можно вместо Light * masterLight использовать std::shared_ptr<Light> masterLight. Тогда код Light::GetIntesity останется таким же, там, похоже, и менять ничего не придётся. Только masterLight теперь будет жить так долго, пока существует хоть один Light, связанный с ним. Правильно это или нет, должно определяться в модели, на этапе проектирования. Там определяется характер связи MasterLight <-> Light. В терминологии UML нужно определить тип агрегации, т.е.  является ли MasterLight частью Light (Light - это "целое", а MasterLight - его "часть")? В терминологии владения аналогичный вопрос: является ли Light владельцем MasterLight? Скорей всего нет. Поэтому в терминах UML тип агрегации AggregationKind::None, а в терминах владения - это слабая (weak) ссылка. Поэтому, немного поломав голову, поле изменится на std::weak_ptr<Light> masterLight.

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

Но это всё лирика, теперь к практике ). ssoft написал пример, и он будет выглядеть так же, если Light * masterLight заменить на  std::weak_ptr<Light> masterLight или upl::weak<Light> masterLight. И если нет разницы, зачем все эти навороты, если хватает стандартных указателей? Проблема возникает с уникальным владением. Допустим, необходимо обеспечить, чтобы только один объект владел masterLight, тогда, по-хорошему, надо использовать std::unique_ptr<Light>, а поле Light::masterLight тогда какого типа будет? Голый указатель? От чего уходили, к тому и вернулись ). UPL как раз позволяет решить эту проблему связкой upl::unique <-> upl::weak.

Ещё один момент: тип параметров. Допустим, нужно "напечатать"  masterLight. Какая сигнатура будет у метода Light::printLight(), в случае, когда Light может храниться в Light *, std::unique/shared/weak_ptr<Light>? Да даже в случае std::shared/weak_ptr<Light>? Допустим: Light::printLight(std::shared<Light>). Тогда, если тип поля std::shared_ptr<Light> masterLight, то можно передать просто masterLight. В случае же std::weak_ptr<Light> masterLight нужно уже передавать или masterLight.lock() или std::shared_ptr<Light>{masterLight}. Т.е. в зависимости от типа поля код программы может изменяться. В UPL есть унифицированный указатель upl::unified, который может создаваться из любого другого типа, и метод Light::printLight(upl::unified<Light>) будет работать с любым типом поля (upl::unique/shared/weak).

Для любителей шаблонов есть концепты указателей, позволяющие гибко определять тип указателей и владения, и шаблонный upl::pointer, с помощью которого можно создать указатель, параметры (тип владения, кратность) которого указываются в параметрах шаблона.


Название: Re: Unified Pointer Library
Отправлено: Igors от Июль 09, 2018, 14:11
ssoft всё правильно написал. Такая конструкция вполне имеет право на существование. Со множеством ЕСЛИ :). Например, ..
Ну особой радости она не доставляет. Простой случай - удаление мастера. Надо пробежаться по всем объектам типа Light и обнулить удаляемого мастера. Но перед этим надо ж сохранить undo - значит еще пробежаться. 

Аналитик/эксперт спросит: a cколько может быть объектов типа Light?  Ну сотни (тысяч пока не было). Значит и проблемы нет, проще = лучше. Не все так просто. Операция сама по себе приемлема по времени, но может попасть в другую с хорошей кратностью. Напр юзер уже не раз просил рисовать мастеров с др иконкой - и тут уже хз так ли безобидны такие пробежки. В общем, данные слишком примитивны, требуется много/больше кода чтобы эту простоту поддерживать.

Обойтись стандартными вумными указателями я могу хотя бы так
Код
C++ (Qt)
struct Light {
...
QSharedPointer<Light> mSharedPtr;  // приватный шаред на себя, создается в конструкторе с пустым Deleter
...
QWeakPointer<Light> GetWeakPtr( void );
};
Думаю что лепить такую городушку не стоит - слишком мал "выйгрышь". Да, теперь можно "не вычеркивать", но на undo все равно прописывать придется. Лучше смотрится простая организация (напоминает парент-чайлд), т.е.

- на объект можно ссылаться, он знает ссылающихся (напр хранит контейнер указателей)
- и наоборот,  объект хранит контейнер тех на которых он сам ссылается

В общем виде это кусок пожирнее чем просто "валиден"(указатель) и "продлить жизнь" (что кстати спорно). Но как к нему подступиться?


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 09, 2018, 18:53
Думаю что лепить такую городушку не стоит - слишком мал "выйгрышь". Да, теперь можно "не вычеркивать", но на undo все равно прописывать придется. Лучше смотрится простая организация (напоминает парент-чайлд), т.е.

- на объект можно ссылаться, он знает ссылающихся (напр хранит контейнер указателей)
- и наоборот,  объект хранит контейнер тех на которых он сам ссылается

В общем виде это кусок пожирнее чем просто "валиден"(указатель) и "продлить жизнь" (что кстати спорно).

Работа программиста в том и заключается, чтобы определять, когда и какую городушку лепить :). Вариантов мильён, зависят от контекста задачи, где и как она должна выполняться, насколько оптимально. Если в одном потоке, то одно решение, если в нескольких, то уже совсем другое может быть. UPL - это не универсальное средство на все случаи жизни, лишь один из инструментов. Основная область применения: написание кода по модели UML, выполнение в многопоточной среде. Кстати, если писать программы в стиле как ssoft предлагает, то модель сразу с кодом появляется ). И в исходниках можно сразу модель увидеть, и не гадать, что и как друг с другом связано. Но это и с UPL можно делать, хоть и не так явно.

Но как к нему подступиться?

Начать с проектирования, модель составить. Она в том коде уже есть, надо её только на свет вынести :). Определить, кто чем владеет, а кто просто ссылается. Выполняться это будет в одном потоке или в нескольких. Потом выбрать оптимальный инструмент. Закодировать. Профит! :)


Название: Re: Unified Pointer Library
Отправлено: ssoft от Июль 10, 2018, 07:56
Начать с проектирования, модель составить.

Согласен).

Необходимо, прежде всего, определиться, что есть Light и какие отношения между Light и его Light::masterLight.

  • Является ли экземпляр master_light частью экземпляра light (должен ли быть удален master_light при удалении light)?
  • Является ли экземпляр master_light уникальным по отношению к light (может ли один и тот же master_light относится к нескольким light)?
  • Сколько различных master_light может относится к light (0, 1, 5, 10, много)?
  • Не существует ли глобального менеджера ресурсов, управляющего всеми экземплярами light?
  • Не определяется ли время жизни экземпляра light лишь областью видимости глобальных/локальных переменных?
  • Предполагается ли конкурентный доступ к одному экземпляру light?
  • Предполагается ли параллельный доступ к разным экземплярам light?

Ответы на эти вопросы позволят сформировать модель отношений и выбрать способ реализации.


Название: Re: Unified Pointer Library
Отправлено: Igors от Июль 10, 2018, 10:18
Начать с проектирования, модель составить.

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

Основной минус любой умности указателей - маломощность. Ну да, можно проверить "не сдох ли" и продлить (жалкую) жизнь, но это по сути и все. Простейший приведенный пример показывает - этого мало, действительно умный указатель должен знать "всех кто на него ссылается", а не так себе "счетчик" (впрочем в Qt и счетчика не дают). Устанавливаем отношения между 2 объектами - значит устанавливаем, а то одному что-то установили (дав на руки weak), а другому вообще ничего. И приходится наращивать  ф-ционал великом - а тогда чего умничали с указателем?

Такое легко сделать "как-нибудь" (напр навязав базовый god класс(ы)) - но вот по-настоящему хорошей реализации я пока не видел. А делать это приходится часто, практически всегда. Вот Вам, разработчикам общих вещей, и карты в руки.


Название: Re: Unified Pointer Library
Отправлено: ssoft от Июль 10, 2018, 10:52
А я нет :) Начинаются бесконечные "выяснения подробностей", после которых ... наступает тишина. Не стоит даже начинать. Общая вещь - значит общая, как ее прикрутить к моему конкретному случаю - мои проблемы, разберусь, было бы что прикручивать.  Вот что хотелось бы.

ИМХО, без модели даже проект не начну. Это как без чертежей и технологии сборки пытаться создать сложную конструкцию или механизм.
Вместо слаженного продукта получается набор костылей. Хотя бы набросок модели должен быть.

Основной минус любой умности указателей - маломощность. Ну да, можно проверить "не сдох ли" и продлить (жалкую) жизнь, но это по сути и все.

Существует большое количество умных указателей, каждый из которых обладает определенными особенностями - полезными свойствами и накладными расходами.
Зная эти характеристики можно подобрать нужный тип указателя исходя их особенностей решаемой задачи.
Используемый инструмент-библиотека, ИМХО, должен обеспечивать легкий способ изменения этих характеристик в случае, если особенности задачи меняются.

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

Простейший приведенный пример показывает - этого мало, действительно умный указатель должен знать "всех кто на него ссылается", а не так себе "счетчик" (впрочем в Qt и счетчика не дают).

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


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 11, 2018, 11:04
Основной минус любой умности указателей - маломощность. Ну да, можно проверить "не сдох ли" и продлить (жалкую) жизнь, но это по сути и все. Простейший приведенный пример показывает - этого мало, действительно умный указатель должен знать "всех кто на него ссылается", а не так себе "счетчик" (впрочем в Qt и счетчика не дают). Устанавливаем отношения между 2 объектами - значит устанавливаем, а то одному что-то установили (дав на руки weak), а другому вообще ничего. И приходится наращивать  ф-ционал великом - а тогда чего умничали с указателем?

Такой функционал не всегда нужен. Знать "всех кто на него ссылается" - это дополнительная функциональность, а не основная (указателя/токена/ассоциативной связи). Одна из целей UPL - классифицировать типы указателей и унифицировать работу с ними. Чтобы когда сделают нужный Вам super_shared_ptr, не приходилось опять пол проекта переписывать, чтобы заменить shared_ptr на его более умную версию. Потому что методам типа Light::printLight(SmartPtr<Light> light) вообще без разницы должно быть, в каком владении находится  light и какая реализация у его умного указателя.

Такое легко сделать "как-нибудь" (напр навязав базовый god класс(ы)) - но вот по-настоящему хорошей реализации я пока не видел. А делать это приходится часто, практически всегда. Вот Вам, разработчикам общих вещей, и карты в руки.

Если Вам приходится делать это часто, практически всегда, вот и сделали бы одни раз хорошую общую реализацию. Сами бы пользовались, да и с другими могли бы поделиться ;).


Название: Re: Unified Pointer Library
Отправлено: Igors от Июль 11, 2018, 13:12
ИМХО, без модели даже проект не начну. Это как без чертежей и технологии сборки пытаться ..
А я его и не начинал :) Это старый (но работающий) код который нужно улучшить. Попробуем прямолинейно. Все проблемы возникают из-за того что неизвестно кто ссылается на мастера, их всякий раз нужно искать "по всему списку объектов" - коряво. Так добавим нужные данные и методы, это выглядит примерно так
Код
C++ (Qt)
struct Light {
..
private:
 Light * m_master;
 QVector<Light *> m_slaves;
..
public:
 void Link2Master( Light * master );
 void UnlinkFromMaster( void );
 bool HasGrandMaster( const Light * master ) const; // пресечь циклическую зависимость
};
 
Реализация очевидна. Ну и в деструкторе нужно подсуетиться. И в копировании (m_slaves копироваться не должен). По-моему общность здесь бросается в глаза. Тем более что с пяток таких мест я помню навскидку (masterMaterial и.т.п)

Если Вам приходится делать это часто, практически всегда, вот и сделали бы одни раз хорошую общую реализацию. Сами бы пользовались, да и с другими могли бы поделиться ;).
Ага, "ну вот и делай свой велик"  :) Но скажите, почему я со стандартных средств ровным счетом ничего не имею? Разве велики - это так уж хорошо/правильно? Неужели у меня какая-то причудливая, редкая задача? Да нет же, самая рядовая/типовая. И какой тогда смысл в "умных" указателях если ума у них - шо у курчонка, а толку с гулькин "нос"?


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 11, 2018, 13:55
ИМХО, без модели даже проект не начну. Это как без чертежей и технологии сборки пытаться ..
А я его и не начинал :) Это старый (но работающий) код который нужно улучшить.

Вот и скажите "спасибо" тем, кто начинал этот проект без модели :). При желании можно было наделать простейших обёрток типа std::observer_ptr, чтобы хотя бы отличать "владеющие" указатели от "ссылающихся". Но это всё лирика, если бы да кабы...

Ага, "ну вот и делай свой велик"  :) Но скажите, почему я со стандартных средств ровным счетом ничего не имею? Разве велики - это так уж хорошо/правильно? Неужели у меня какая-то причудливая, редкая задача? Да нет же, самая рядовая/типовая. И какой тогда смысл в "умных" указателях если ума у них - шо у курчонка, а толку с гулькин "нос"?

Задача  может типовая, но решений может быть множество под разные требования. Кому-то достаточно реализации на голых указателях в одном потоке, другим на умных указателях, чтоб про ручное удаление объектов забыть, третьим подавай потокобезопасность, четвёртым события, что добавилась/удалилась ссылка и т.д. Такие общие решения "паттернами" называют, и каждый реализацию сам делает, под свои потребности. Или готовые в сторонних библиотеках ищет. Велики - это плохо, но всё подряд в стандартную библиотеку тащить, может быть ещё хуже. Значит ещё не накопилась критическая масса желающих такого функционала и не выработано наиболее общее решение, которое устроит большинство, раз такой штуки нет в стандартной библиотеке.


Название: Re: Unified Pointer Library
Отправлено: ViTech от Июль 16, 2018, 12:04
Заменил реализацию указателей UPL на обёртки над указателями std. Теперь upl::unique/shared/unified - это обёртки над std::shared_ptr, а upl::weak - обёртка над std::weak_ptr. При этом несколько уменьшился диапазон возможностей указателей, но появилась более тесная интеграция с указателями std. upl::unique можно создавать из std::unique_ptr,  upl::shared/unified из std::shared_ptr, upl::weak из std::weak_ptr. В обратную сторону работает только получение std::shared_ptr из upl::shared.  Так что если уникальное владение делать через std::shared_ptr, то, при замене его на upl::unique, накладные расходы в run-time останутся такими же. Может несколько увеличиться время компиляции (не проверял ещё), но это может компенсироваться более понятными ошибками компиляции, которые я добавил для указателей upl. Также стоит обратить внимание на более удобный и унифицированный доступ к значению указателя через upl::access().

В качестве примера можно рассмотреть взаимодействие объектов трёх типов: Car, Engine и Monitor. Car уникально владеет одним Engine, несколько Monitor могут наблюдать за одним Engine, Car и Monitor независимы друг от друга (UML диаграмма классов в рисунке вложения). Эмулируется работа объектов в разных потоках. Составляется очередь команд, которая тоже выполняется в отдельном потоке. Основной код примера:
Код
C++ (Qt)
class Printable
{
public:
   virtual ~Printable() {}
   virtual void print() const = 0;
};
 
template <class P>
std::enable_if_t<upl::Pointer<P, const Printable>>
print(const P& pointer)
{
#if 1
   // Access to the 'pointer' by the lambda.
   upl::access(pointer,
               [](auto& value) { value.print(); },
               [] { std::cout << "no value" << std::endl; });
#else
   // Similar access to the 'pointer' by the "standard" way.
   const auto& pointer_accessor = upl::access(pointer);
   if (pointer_accessor)
   {
       auto& value = *pointer_accessor;
       value.print();
   }
   else
   {
       std::cout << "no value" << std::endl;
   }
#endif
}
 
class Engine : public Printable
{
public:
   Engine() {}
   Engine(const std::string& model) : m_model{model} {}
   ...
   std::string model() const { return m_model; }
   int  currentRpm() const   { return m_current_rpm; }
 
   void accelerate() { m_current_rpm++; }
   ...
private:
   std::string m_model;
   int m_current_rpm{0};
};
 
class Car : public Printable
{
public:
   using SingleEngine = upl::unique_single<Engine>;
 
   Car(const std::string& brand, SingleEngine engine)
       : m_brand{brand}, m_engine{std::move(engine)}
   {}
 
   SingleEngine replaceEngine(SingleEngine engine)
   {
       m_engine.swap(engine);
       return engine;
   }
 
   void drive() { (*m_engine).accelerate(); }
 
   std::string brand() const { return m_brand; }
   ...
private:
   std::string  m_brand;
   SingleEngine m_engine;
};
 
class Monitor : public Printable
{
public:
   using WeakEngine = upl::weak<const Engine>;
 
   Monitor() {}
   Monitor(const std::string& model, const WeakEngine& engine)
       : m_model{model}, m_engine{engine}
   {}
 
   std::string model() const { return m_model; }
 
   void setEngine(const WeakEngine& engine)
   { m_engine = engine; }
   ...
private:
   std::string m_model;
   WeakEngine  m_engine;
};
 
void driveCar(upl::weak<Car> car)
{
   using namespace std::chrono_literals;
 
   for (int i = 0; i < 20; ++i)
   {
       std::this_thread::sleep_for(40ms);
       upl::access(car, [](auto& car) { car.drive(); });
   }
}
 
void monitorEngine(upl::weak<const Monitor> monitor)
{
   using namespace std::chrono_literals;
 
   for (int i = 0; i < 10; ++i)
   {
       std::this_thread::sleep_for(100ms);
       upl::access(monitor, [](auto& monitor) { monitor.print(); });
   }
}
...
int main()
{
   // With the 'upl::itself' parameter, the 'upl::unique' creates the 'Engine'
   // in the same way as the 'std::make_unique<Engine>'.
   upl::unique<Engine> vaz_i4{upl::itself, "VAZ I4"};
   upl::unique<Engine> zmz_i4{upl::itself, "ZMZ I4"};
 
   upl::shared<Monitor> monitor_1{upl::itself, "ME 1", vaz_i4};
   upl::unique<Monitor> monitor_2{upl::itself, "ME 2", zmz_i4};
 
   upl::shared<Car> lada{upl::itself, "LADA", std::move(vaz_i4)};
   upl::unique<Car> uaz{upl::itself, "UAZ", std::move(zmz_i4)};
 
   std::vector<std::thread> threads;
   // Drive cars in separate threads.
   addThread(threads, [&] { driveCar(lada); });
   addThread(threads, [&] { driveCar(uaz); });
   // Monitor engines in separate threads.
   addThread(threads, [&] { monitorEngine(monitor_1); });
   addThread(threads, [&] { monitorEngine(monitor_2); });
 
   using EngineStore = upl::shared<std::vector<Car::SingleEngine>>;
   EngineStore engine_store{upl::itself};
   (*engine_store).emplace_back(upl::itself, "VAZ V4 Turbo");
 
   std::queue<std::function<void()>> commands;
 
   commands.emplace([ = ] // Capture the shared 'engine_store' and the 'lada' by value.
   {
       auto& store = (*engine_store);
       auto engine = (*lada).replaceEngine(std::move(store.back()));
       store.pop_back();
       store.push_back(std::move(engine));
   });
 
   commands.emplace( // Move the unique 'uaz' into the command with the 'upl::unique_carrier' helper.
       [uaz = upl::unique_carrier{std::move(uaz)}]() mutable
       { auto wall = std::move(uaz); });
 
   commands.emplace( // Capture the unique 'monitor_2' by the upl::weak.
       [ =, m = upl::weak<Monitor>{monitor_2}]
       { upl::access(m, [&](auto& m) { m.setEngine((*engine_store)[0]); }); });
 
   // Execute commands in a separate thread.
   addThread(threads, [&] { execute(std::move(commands)); });
 
   for (auto& thread:threads)
       thread.join();
 
   print(lada);
   print(uaz);
 
   return 0;
}

Проект примера с полным кодом и библиотекой UPL находится во вложении UplCar.zip. Более полные материалы располагаются в репозитории Car (https://gitlab.com/UnifiedPointers/Example/Car). Можете поэкспериментировать с примером, попробовать "поломать" модель (установить один двигатель в два разных авто или ещё что :)), написать те же классы, только с использованием стандартных указателей (с соблюдением требований (https://gitlab.com/UnifiedPointers/Example/Car/blob/master/doc/ru/Readme.md#%D1%82%D1%80%D0%B5%D0%B1%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F-%D0%BA-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B5) к программе) и сравнить, какой способ лучше.

В качестве более "продвинутого" использования UPL (в частности концептов), можете посмотреть на тесты (https://gitlab.com/CppUml/Test/Detail/Owner). Например, в одном этом методе (https://gitlab.com/CppUml/Test/Detail/Owner/blob/master/include/Test/Owner/Construction/Move.h#L48) выполняется создание указателей с объектами разного типа, разным типом владения, разной кратностью и разной реализацией указателей. А здесь (https://gitlab.com/CppUml/Test/Detail/Owner/blob/master/include/Test/Owner/Construction/Move.h#L486) вызываются функции для проверки указателей по заданным правилам, согласно типу владения и реализации.