Russian Qt Forum
Март 29, 2024, 09:35 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: 1 [2]   Вниз
  Печать  
Автор Тема: Unified Pointer Library  (Прочитано 12730 раз)
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #15 : Июль 09, 2018, 18:53 »

Думаю что лепить такую городушку не стоит - слишком мал "выйгрышь". Да, теперь можно "не вычеркивать", но на undo все равно прописывать придется. Лучше смотрится простая организация (напоминает парент-чайлд), т.е.

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

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

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

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

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

Пока сам не сделаешь...
ssoft
Программист
*****
Offline Offline

Сообщений: 574


Просмотр профиля
« Ответ #16 : Июль 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?

Ответы на эти вопросы позволят сформировать модель отношений и выбрать способ реализации.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #17 : Июль 10, 2018, 10:18 »

Начать с проектирования, модель составить.

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

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

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

Сообщений: 574


Просмотр профиля
« Ответ #18 : Июль 10, 2018, 10:52 »

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

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

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

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

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

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

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

Сообщений: 858



Просмотр профиля
« Ответ #19 : Июль 11, 2018, 11:04 »

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

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

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

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

Пока сам не сделаешь...
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #20 : Июль 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 и.т.п)

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

Сообщений: 858



Просмотр профиля
« Ответ #21 : Июль 11, 2018, 13:55 »

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

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

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

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

Пока сам не сделаешь...
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #22 : Июль 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. Можете поэкспериментировать с примером, попробовать "поломать" модель (установить один двигатель в два разных авто или ещё что Улыбающийся), написать те же классы, только с использованием стандартных указателей (с соблюдением требований к программе) и сравнить, какой способ лучше.

В качестве более "продвинутого" использования UPL (в частности концептов), можете посмотреть на тесты. Например, в одном этом методе выполняется создание указателей с объектами разного типа, разным типом владения, разной кратностью и разной реализацией указателей. А здесь вызываются функции для проверки указателей по заданным правилам, согласно типу владения и реализации.
Записан

Пока сам не сделаешь...
Страниц: 1 [2]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.12 секунд. Запросов: 23.