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

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

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: QSharedPointer и/или implicit sharing?  (Прочитано 8552 раз)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« : Апрель 21, 2019, 08:24 »

Добрый день

Типовая ситуация: есть объекты которые могут интенсивно дублироваться. Объекты имеют весомые данные которые хотелось бы иметь шареными, обычно всякие контейнеры (но не объекты целиком). Однако в любой момент юзер может изменить любую копию, и она становится уникальной. Затем может и нажать undo - нужно как-то опять зашарить. Также есть возможность изменить сразу N копий, если делать по уму, их тоже надо шарить. Также I/O - нужно как-то поддерживать шаринг на записи и чтении.

Конечно "как-то" сделать можно, но хотелось бы тщательнее продумать перед реализацией. Поделитесь опытом.

Спасибо

 
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #1 : Апрель 21, 2019, 22:56 »

Забавно как этот анду постоянно вылезает:)

Но собственно "зашарить обратно"... зачем? Как вы храните анду реду? Диффами или стейтом? Если диффами, то ничего шарить не надо - на старый стейт применили дифф, получили новый, итого объект один. Если стейтом, то надо всегда "шарить" иначе потеряется новый стейт при анду и реду сделать не выйдет.

Как всегда в постановке не видно проблемы, но щаз выяснятся "детали"
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #2 : Апрель 22, 2019, 07:45 »

Забавно как этот анду постоянно вылезает:)
Это норм, undo - прекрасная проверка архитектуры на вшивость

Но собственно "зашарить обратно"... зачем? Как вы храните анду реду? Диффами или стейтом? Если диффами, то ничего шарить не надо - на старый стейт применили дифф, получили новый, итого объект один. Если стейтом, то надо всегда "шарить" иначе потеряется новый стейт при анду и реду сделать не выйдет.

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

Ладно, копнем. Диффы сразу отбросим. Я думаю по сути тут решение одно: мапа/хеш, и простая логика типа: данные с таким ключом есть в хеше? Да - берем их, нет - заводим новые и помещаем в хеш. Подобная задачка когда-то у меня была, запомнилось что применить вумные указатели не удалось. В самом деле - если в хеше сидит QSharedPointer - клиент его не удалит. Можно хранить в хеше weak, но это выглядит как-то странно, да и неаккуратно - надо как-то подчищать  неиспользуемые трупики. В общем тогда я ничего не придумал и сделал по-народному со счетчиком ссылок.

Актуально редактирование - хотелось бы иметь поведение имплисит шары, но с учетом мапы/хеша.

 
« Последнее редактирование: Апрель 22, 2019, 07:48 от Igors » Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #3 : Апрель 22, 2019, 08:03 »

Никаких хешей не нужно.

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

Танцы вокруг хеша могут существенно нагрузить логику программы и процессор. Частый поиск в хеше, необходимость чистки и т.п. еще тот геморрой.
Записан
RedDog
Частый гость
***
Offline Offline

Сообщений: 221


Просмотр профиля
« Ответ #4 : Апрель 22, 2019, 09:11 »

Танцы вокруг хеша могут существенно нагрузить логику программы и процессор. Частый поиск в хеше, необходимость чистки и т.п. еще тот геморрой.
Зачем тогда в компе процессор нужен, если его не нагружать?
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #5 : Апрель 22, 2019, 10:04 »

Зачем тогда в компе процессор нужен, если его не нагружать?
Чтобы эффективно нагружать полезной логикой, а не просто воздух греть).
Тем более, что здесь речь идет о высокопроизводительной графике, всяких fps60+))).
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #6 : Апрель 22, 2019, 12:02 »

Никаких хешей не нужно.

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

Танцы вокруг хеша могут существенно нагрузить логику программы и процессор. Частый поиск в хеше, необходимость чистки и т.п. еще тот геморрой.
Ничего не понял. Вот пример/псевдокод чего я хочу
Код
C++ (Qt)
void SetIndex( SomeClass & cnt, int index, int value )
{
 Data & data = cnt.data();  // поведение QSharedPointer
 data[index] = value;         // поведение implicit sharing
}
При этом если измененный контейнер данных уже существует - он должен шариться (вместо создания нового как делает Qt имплисит шара). Не вижу как тут обойтись без хеша/мапы

Чтобы эффективно нагружать полезной логикой, а не просто воздух греть).
Тем более, что здесь речь идет о высокопроизводительной графике, всяких fps60+))).
Тогда для Вас свежая задачка в Говорилка > Геометрия  Улыбающийся Увы, за хорошими словами часто скрывается любовь не к графике, а богомерзким железкам (подороже)
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #7 : Апрель 22, 2019, 15:45 »

На вскидку такой прототип решения
Код
C++ (Qt)
#include <vector>
#include <QByteArray>
 
class Action
{
public:
   virtual ~Action () {}
   virtual void undo () = 0;
   virtual void redo () = 0;
};
 
class Actions
{
   using OwnAction = ::std::unique_ptr< Action >;
   using OwnActions = ::std::vector< OwnAction >;
 
   OwnActions m_actions;
   OwnActions::size_type m_cursor = 0;
 
public:
   static Actions & instance () { static Actions singletone; return singletone; }
 
   template < typename _Action, typename ... _Arguments >
   static void apply ( _Arguments && ... arguments )
   {
       auto & actions = instance().m_actions;
       auto & cursor = instance().m_cursor;
       actions.resize( cursor++ );
       actions.push_back( ::std::make_unique< _Action >( ::std::forward< _Arguments >( arguments ) ... ) );
       actions.back()->redo();
   }
 
   static bool undo ()
   {
       auto & actions = instance().m_actions;
       auto & cursor = instance().m_cursor;
 
       if ( actions.empty() || cursor == 0 )
           return false;
 
       actions[ --cursor ]->undo();
       return true;
   }
 
   static bool redo ()
   {
       auto & actions = instance().m_actions;
       auto & cursor = instance().m_cursor;
 
       if ( cursor >= actions.size() )
           return false;
       actions[ cursor++ ]->redo();
       return true;
   }
};
 
struct SomeClass
{
   using Data = QByteArray;
   Data implicit_shared_data;
};
 
class SetIndexAction
   : public Action
{
   SomeClass & m_object;
 
   // состояния до и после
   SomeClass::Data m_state_before;
   SomeClass::Data m_state_after;
 
public:
   SetIndexAction( SomeClass & cnt, int index, int value )
       : m_object( cnt )
       , m_state_before( cnt.implicit_shared_data )
   {
       SomeClass::Data & data = cnt.implicit_shared_data;
       data[ index ] = value;  // detach and copy
       m_state_after = cnt.implicit_shared_data;
   }
 
   void undo () final { m_object.implicit_shared_data = m_state_before; }
   void redo () final { m_object.implicit_shared_data = m_state_after; }
};
 
void SetIndex ( SomeClass & cnt, int index, int value )
{
   Actions::apply< SetIndexAction >( cnt, index, value );
       // применили действие
   Actions::undo();
       // отменили
   Actions::redo();
       // повторили
}
 
« Последнее редактирование: Апрель 22, 2019, 16:03 от ssoft » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #8 : Апрель 23, 2019, 09:21 »

На вскидку такой прототип решения
Ой, мама  Улыбающийся По поводу undo - у меня оно льется в файл перед тем как данные "честно" удаляются. При выполнении undo - читаются из файла. Т.е. ситуация в принципе такая же как и "юзер изменил данные", только источник изменений другой.

Хотелка - шарить вновь прибывшие данные (без разницы откуда они пришли). Конечно это имеет смысл если дубликатов ожидается много - ну так оно и есть. "Не мудрствуя лукаво" это можно сделать напр так (распишем пример выше)
Код:
void SetIndex( SomeClass & target, int index, int value )
{
// получаем локальную копию шареных данных
  Data data = target.CopyDown();

// меняем локальную копию
// возможно изменений будет много
  data[index] = value;        

// заряжаем измененные данные
// если они уже имеются, то шарятся
  target.CopyUp(data);
}
В простейшем случае target - просто ключ, тогда CopyDown - копирование данных этого ключа, а CopyUp - поиск в хеше/мапе или ее пополнение, в результате target получает новый ключ. Возможно приятнее сделать Data "scoped" и поместить CopyUp/Down в конструктор/деструктор.

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

Такое же недоумение по поводу организации хеша/мапы (кстати о птичках: выбор между ними здесь не очевиден). Как и неск лет назад, я в упор не вижу ничего лучшего чем завести ручной счетчик ссылок.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #9 : Апрель 24, 2019, 10:45 »

Так ну если состояние в файл сброшено, то что шарить?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Апрель 24, 2019, 14:23 »

Так ну если состояние в файл сброшено, то что шарить?
Сама запись в файл ничего не меняет, все как и было так и осталось шареным. Но если данные удалены а затем читаются из файла (нажато undo), то они "примкнут" к шареным, новых экземпляров создаваться не будет.
Записан
ssoft
Программист
*****
Offline Offline

Сообщений: 579


Просмотр профиля
« Ответ #11 : Апрель 24, 2019, 15:05 »

Чтобы восстановить шару, нужен какой-то ключ. Либо контрольная сумма, либо любой другой уникальный признак, чтобы отобразить его в конкретную шару.
Если такой признак получить невозможно, то придется сравнивать сами данные. А это может быть ресурсоемко (создать->сравнить со всеми->удалить, если такие данные уже есть).
В любом случае требуется наличие некоторого менеджера шар, который собственно их восстановлением и будет заниматься.
Так как это частный случай, то менеджер шар придется писать самостоятельно.

А случай здесь частный, так как признак отображения и способ отображения признака в шару зависят от конкретной прикладной задачи.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #12 : Апрель 24, 2019, 15:38 »

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

Если такой признак получить невозможно, то придется сравнивать сами данные. А это может быть ресурсоемко (создать->сравнить со всеми->удалить, если такие данные уже есть).
Да, сравнение напр вектора индексов может быть затратно.

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

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

Пример: банальное текстурирование объектов - каждый может иметь N текстур, при этом даже на одном объекте они частенько повторяются, а сами объекты могут интенсивно дублироваться. Так что мне, грузить все текстуры подряд? Это просто не катит т.к. может занять десятки минут, да и карта наверное свалится в эмуляцию.



Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #13 : Апрель 24, 2019, 15:48 »

Ну хорошо, вот хотелось бы поговорить об организации мапы/хеша. Я вижу такую простую схему

- все по образцу QCache - может и унаследоваться от него. А вот в тип T (хранимое значение) добавить методы ref/deref, ну и руками их +/-

Практически меня это вполне устраивает, но смущает добровольный и полный отказ от вумных указателей, которые здесь, вроде бы, очень "в масть". Как же так - явный, махровый шаринг, а применить стандартные средства не удается... Или я не прав?
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3258


Просмотр профиля
« Ответ #14 : Апрель 24, 2019, 15:57 »

Ну так у вас "сохранение в файл" основная проблема, а не "букварь".
Вот у вас есть шаренный кустринг, вы записали его в файл дважды. Потом файл прочитали и ОПА получили два кустринга. Как так вышло-то?
Тут надо уходить от коровы и писать на шаред_птрах всё. Ну или на ExcplicitlySharedPointer. В любом случае, должно быть явно видно, что пошарено, а что нет. С точки зрения пользователя (то есть вас) пошаренный кустринг - это ДВА РАЗНЫХ кустринга. На то он и неявный шаринг.
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


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