Unified Pointer Library (описание на русском) — библиотека унифицированных указателей (UPL), содержит концепты и реализации умных указателей, которые предназначены для управления временем жизни объектов. Предоставляет указатели upl::unique и upl::shared для уникального и совместного владения объектами, слабые ссылки для них upl::weak, и добавляет унифицированный тип владения upl::unified. Публичный интерфейс унифицированных указателей схож с интерфейсом умных указателей стандартной библиотеки C++.
Ключевые особенности:
Возможность организации ассоциативных связей между объектами в соответствии с UML.
Определены концепты, которые в compile-time позволяют гибко определять тип указателей UPL в обобщённых алгоритмах.
Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
Указатель upl::unified позволяет передать уникальное владение объектом в цепочке, где может выполняться копирование.
С помощью upl::unified можно временно продлить время жизни объекта в заданной области видимости, что позволяет корректно завершить работу с ним, даже когда все остальные указатели на этот объект удалены.
Добавлены указатели с одинарной кратностью, которые не могут быть пустыми и всегда ссылаются на один объект.
Здравствуйте, ViTech, Вы писали:
VT>Делаю такую штуку. Может ещё кому пригодится.
VT>Unified Pointer Library (описание на русском) — библиотека унифицированных указателей (UPL), содержит концепты и реализации умных указателей, которые предназначены для управления временем жизни объектов. Предоставляет указатели upl::unique и upl::shared для уникального и совместного владения объектами, слабые ссылки для них upl::weak, и добавляет унифицированный тип владения upl::unified. Публичный интерфейс унифицированных указателей схож с интерфейсом умных указателей стандартной библиотеки C++.
Круто! Ждём Unified Math Library, Unified GUI Libaray и Unified Coroutine and Threading Library.
Здравствуйте, ViTech, Вы писали:
VT>Делаю такую штуку. Может ещё кому пригодится.
VT>Unified Pointer Library (описание на русском) — библиотека унифицированных указателей (UPL), содержит концепты и реализации умных указателей, которые предназначены для управления временем жизни объектов. Предоставляет указатели upl::unique и upl::shared для уникального и совместного владения объектами, слабые ссылки для них upl::weak, и добавляет унифицированный тип владения upl::unified. Публичный интерфейс унифицированных указателей схож с интерфейсом умных указателей стандартной библиотеки C++.
какие проблемы решались при создании библиотеки, которые не могут решить стандартные указатели?
Целью проекта UPL является создание инструментальной библиотеки для повышения качества и удобства программирования на языке C++ в стиле ООП, в частности для организации ассоциативных связей между объектами в многопоточной среде.
UPL предоставляет расширенную семантику для концепций владения и управления временем жизни объектов, по сравнению с умными указателями стандартной библиотеки C++. Больше внимания уделяет указателям с уникальным владением (слабые ссылки для них, возможность передачи в функторы, которые требуют копирование аргументов (std::function)), обеспечивает продление времени жизни объекта в заданной области видимости, добавляет указатели с одинарной кратностью, которые всегда ссылаются на один объект.
Указатели UPL не являются заменой умных указателей стандартной библиотеки C++ и могут использоваться совместно с ними (конечно, одновременно объект может находиться под управлением только одной библиотеки). UPL предназначена для случаев, когда не хватает функциональности умных указателей стандартной библиотеки C++ и требуются дополнительные возможности для организации связей между объектами в многопоточной среде.
Например, с этими двумя пунктами у меня возникли затруднения при решении с помощью стандартных указателей:
Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
Указатель upl::unified позволяет передать уникальное владение объектом в цепочке, где может выполняться копирование.
Буду признателен, если предложите решение этих пунктов с помощью стандартных указателей (с учётом работы в многопоточной среде).
Здравствуйте, Alexander G, Вы писали: VT>> Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique. AG>Как это сделано? все указатели сделаны поверх типичной реализации shared, даже unique?
Да, реализация типичная — на счётчиках ссылок. Более того, сейчас она довольно наивная, чтобы не впадать в преждевременную оптимизацию. upl::unique тоже ссылается на контрольный блок со счётчиками, так что он тяжелее, чем std::unique_ptr. Хорошо было бы сделать реализацию на чём-то готовом, например std::shared_ptr, но у меня не получилось, поэтому пришлось изобретать велосипед.
Может это странно прозвучит, но сейчас конкретная реализация не так важна. Она может быть и на другом принципе основана, не обязательно подсчёт ссылок. Для меня более важно определить, какой набор указателей является необходимым и достаточным для поставленной цели (см. выше). Например, можно ли обойтись без upl::unified. Также ставилась задача определить концепты для указателей, чтобы шаблонные алгоритмы не были привязаны к конкретным реализациям указателей.
Здравствуйте, ViTech, Вы писали:
NB>>какие проблемы решались при создании библиотеки, которые не могут решить стандартные указатели?
VT>Например, с этими двумя пунктами у меня возникли затруднения при решении с помощью стандартных указателей: VT> Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
что с таким указателем можно делать кроме как проверить жив ли объект или нет?
VT> Указатель upl::unified позволяет передать уникальное владение объектом в цепочке, где может выполняться копирование.
Здравствуйте, night beast, Вы писали:
VT>>Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique. NB>что с таким указателем можно делать кроме как проверить жив ли объект или нет?
Можно:
С помощью upl::unified можно временно продлить время жизни объекта в заданной области видимости, что позволяет корректно завершить работу с ним, даже когда все остальные указатели на этот объект удалены.
Пример использования.
VT>>Указатель upl::unified позволяет передать уникальное владение объектом в цепочке, где может выполняться копирование. NB>что при этом происходит с остальными шаредами?
Уникальное владение из upl::unique нужно перемещать в upl::unified, который по пути может и копироваться. При попытке передать владение в upl::unique из другого указателя, будет выброшено исключение, если объект уже находится в чьём-то владении. Пример TransferUnique.
Здравствуйте, ViTech, Вы писали:
VT>>>Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique. NB>>что с таким указателем можно делать кроме как проверить жив ли объект или нет?
VT>Можно: VT>
VT>С помощью upl::unified можно временно продлить время жизни объекта в заданной области видимости, что позволяет корректно завершить работу с ним, даже когда все остальные указатели на этот объект удалены.
при попытке использовать викптр объект из основного перемещается и по завершении обратно не возвращается?
какие реальные задачи решает такой странный юзкейс?
VT>>>Указатель upl::unified позволяет передать уникальное владение объектом в цепочке, где может выполняться копирование. NB>>что при этом происходит с остальными шаредами?
VT>Уникальное владение из upl::unique нужно перемещать в upl::unified, который по пути может и копироваться. При попытке передать владение в upl::unique из другого указателя, будет выброшено исключение, если объект уже находится в чьём-то владении. Пример TransferUnique.
хз. имхо это все совершенно бесполезная фигня, создающая проблемы на ровном месте.
Здравствуйте, ViTech, Вы писали:
VT>Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
Какая-то шиза в чистом виде. unique_ptr предназначен для ситуаций, когда должен быть только один указатель на объект. И владение unique_ptr-ом определяет время жизни объекта, на который ссылаются. Причем, с точки зрения эффективности реализации (очень важной, поскольку мы в C++) ключевой момент в том, что unique_ptr не использует никакой формы подсчета ссылок (т.е. нет скрытых накладных расходов).
Если это не так, т.е. по условиям задачи вам нужна сильная ссылка на объект (для определения времени его жизни) и, в то же время, слабые ссылки, то вам нужен не unique_ptr, а как раз таки shared_ptr.
Зачем может потребоваться unique, который не unique, а суть shared, понять из ваших наукообразных определений решительно не удается.
Здравствуйте, night beast, Вы писали:
NB>при попытке использовать викптр объект из основного перемещается и по завершении обратно не возвращается? NB>какие реальные задачи решает такой странный юзкейс?
Юзкейс внезапной смерти объекта, находящегося в уникальном владении, при наличии слабых ссылок на него.
NB>хз. имхо это все совершенно бесполезная фигня, создающая проблемы на ровном месте.
Для вас фигня, а для меня нет. Кому-то и raw pointers хватает .
Здравствуйте, ViTech, Вы писали:
VT>[] Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
Что то у меня порвался шаблон немного
1. "upl::unique" — явно предполагает "уникальность" владения хранимым по указателю объектом
2. upl::weak over upl::unique — уничтожает эту самую уникальность, так как указываемый объект теперь доступен более чем из одного указателя
Здравствуйте, so5team, Вы писали:
VT>>Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
S>Какая-то шиза в чистом виде. unique_ptr предназначен для ситуаций, когда должен быть только один указатель на объект. И владение unique_ptr-ом определяет время жизни объекта, на который ссылаются. Причем, с точки зрения эффективности реализации (очень важной, поскольку мы в C++) ключевой момент в том, что unique_ptr не использует никакой формы подсчета ссылок (т.е. нет скрытых накладных расходов).
Если вам нужен такой вариант использования, std::unique_ptr для этого и есть. Если смотреть с точки зрения ассоциативных связей UML, то уникальность — это свойство ассоциативной связи в объекте-владельце, а не объекта, на который ссылаются.
S>Если это не так, т.е. по условиям задачи вам нужна сильная ссылка на объект (для определения времени его жизни) и, в то же время, слабые ссылки, то вам нужен не unique_ptr, а как раз таки shared_ptr.
S>Зачем может потребоваться unique, который не unique, а суть shared, понять из ваших наукообразных определений решительно не удается.
Эти "наукообразные определения" особо-то и не мои, они в UML описаны. Если вам не понятен раздел Ассоциативные связи, значит вам эта библиотека не нужна.
Здравствуйте, ViTech, Вы писали:
NB>>при попытке использовать викптр объект из основного перемещается и по завершении обратно не возвращается? NB>>какие реальные задачи решает такой странный юзкейс?
VT>Юзкейс внезапной смерти объекта, находящегося в уникальном владении, при наличии слабых ссылок на него.
нет. какой юз кейс слабых ссылок, которые забирают владение?
Здравствуйте, ViTech, Вы писали:
VT>Если вам нужен такой вариант использования, std::unique_ptr для этого и есть. Если смотреть с точки зрения ассоциативных связей UML, то уникальность — это свойство ассоциативной связи в объекте-владельце, а не объекта, на который ссылаются.
Или следствие бардака в вашей голове из-за которого вы не можете разложить уже существующие типы умных указателей "по полочкам" и придумываете свои велосипеды, прикрываясь наукообразием. При этом не исключено, что ассоциативность из UML вы так же не понимаете из-за того же бардака.
S>>Зачем может потребоваться unique, который не unique, а суть shared, понять из ваших наукообразных определений решительно не удается.
VT>Эти "наукообразные определения" особо-то и не мои, они в UML описаны.
Скорее всего есть огромная разница между:
тем, что описано в UML;
тем, что восприняли вы;
тем, как вы свое восприятие "положили" на возможности C++.
VT>Если вам не понятен раздел Ассоциативные связи, значит вам эта библиотека не нужна.
Синдром непризнанного гения?
Допустим, что это не так, что вы действительно сделали полезную штуку. Объясните, пожалуйста, в каких условиях она кому-нибудь вообще может потребоваться. Пока во всей теме вменяемых объяснений еще не было.
Здравствуйте, night beast, Вы писали:
NB>нет. какой юз кейс слабых ссылок, которые забирают владение?
Они не забирают владение. Они позволяют предоставлять доступ к объекту в функции/методе, и поддержать жизнь объекта на это время, с учётом того, что в другом потоке, в это же время, исходный upl::unique может удалиться. Если же попытаться из weak получить unified и затем unique, то должно выбрасываться исключение, если на объект есть другая строгая ссылка.
Здравствуйте, vopl, Вы писали:
V>Здравствуйте, ViTech, Вы писали:
VT>>[] Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
V>Что то у меня порвался шаблон немного V>1. "upl::unique" — явно предполагает "уникальность" владения хранимым по указателю объектом V>2. upl::weak over upl::unique — уничтожает эту самую уникальность, так как указываемый объект теперь доступен более чем из одного указателя
V>получилась не уникальная уникальность?
Если объект находится под управлением std::unique_ptr, то больше никто и никогда не может обращаться к этому объекту (кроме владельца)? Я рассматриваю владение с точки зрения ассоциативных связей UML, где weak для unique — это противоположный полюс ассоциации. С такого угла зрения есть какое-либо противоречие?
Здравствуйте, ViTech, Вы писали:
NB>>нет. какой юз кейс слабых ссылок, которые забирают владение?
VT>Они не забирают владение. Они позволяют предоставлять доступ к объекту в функции/методе, и поддержать жизнь объекта на это время, с учётом того, что в другом потоке, в это же время, исходный upl::unique может удалиться. Если же попытаться из weak получить unified и затем unique, то должно выбрасываться исключение, если на объект есть другая строгая ссылка.
то есть на время предоставления доступа к объекту в функции/методе существует два уника (уник со счетчиком ссылок = 2)?
Здравствуйте, night beast, Вы писали:
VT>>Они не забирают владение. Они позволяют предоставлять доступ к объекту в функции/методе, и поддержать жизнь объекта на это время, с учётом того, что в другом потоке, в это же время, исходный upl::unique может удалиться. Если же попытаться из weak получить unified и затем unique, то должно выбрасываться исключение, если на объект есть другая строгая ссылка.
NB>то есть на время предоставления доступа к объекту в функции/методе существует два уника (уник со счетчиком ссылок = 2)?
Нужно разделять понятия "владение" и "доступ". К объекту, находящемуся в уникальном владении, другие объекты могут получать доступ? Я считаю, что да, могут. Но они не могут просто так получить владение им. Так что в функции/методе владелец будет один, а доступ через unified получить можно много раз, и держать его (доступ), в случае необходимости. В текущей реализации для строгого владения (unique, shared) и для доступа (unified) используются разные счётчики.
Здравствуйте, so5team, Вы писали:
S>Допустим, что это не так, что вы действительно сделали полезную штуку. Объясните, пожалуйста, в каких условиях она кому-нибудь вообще может потребоваться. Пока во всей теме вменяемых объяснений еще не было.
Эта штука, в первую очередь, нужна мне. Мне она нужна в условиях работы в многопоточной среде для организации ассоциативных связей между объектами. В частности, для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей. Если взять пример TransferUnique, то я хочу, чтобы Car уникально владел Engine. И чтобы можно было передать владение Engine или из фабрики, или сняв с другого Car, через очередь сообщений/команд. std::unique_ptr в std::function<void()> не лезет. Можно сырые указатели пересылать, но мне это не нравится.
Здравствуйте, ViTech, Вы писали:
VT>>>[] Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique.
V>>Что то у меня порвался шаблон немного V>>1. "upl::unique" — явно предполагает "уникальность" владения хранимым по указателю объектом V>>2. upl::weak over upl::unique — уничтожает эту самую уникальность, так как указываемый объект теперь доступен более чем из одного указателя
V>>получилась не уникальная уникальность?
VT>Если объект находится под управлением std::unique_ptr, то больше никто и никогда не может обращаться к этому объекту (кроме владельца)? Я рассматриваю владение с точки зрения ассоциативных связей UML, где weak для unique — это противоположный полюс ассоциации. С такого угла зрения есть какое-либо противоречие?
С такого — противоречий нет. Оригинальный вопрос был не про противоположный полюс.
class Car;
class Engine;
class Car
{
upl::unique<Engine> engineStrong; //рассматриваем этот unique как полюс ассоциации "машина [0,1] -- [0,1] двигатель"
upl::weak<Engine> engineWeak; //альтернативный полюс для engineStrong. Вот про него речь. С ним проблемы.
};
class Engine
{
unspecified<Car> car; //противоположный для engineStrong+engineWeak, может быть upl::weak, а может что то другое. Не интересует
};
Здравствуйте, ViTech, Вы писали:
S>>Допустим, что это не так, что вы действительно сделали полезную штуку. Объясните, пожалуйста, в каких условиях она кому-нибудь вообще может потребоваться. Пока во всей теме вменяемых объяснений еще не было.
VT>Эта штука, в первую очередь, нужна мне. Мне она нужна в условиях работы в многопоточной среде для организации ассоциативных связей между объектами. В частности, для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей. Если взять пример TransferUnique, то я хочу, чтобы Car уникально владел Engine. И чтобы можно было передать владение Engine или из фабрики, или сняв с другого Car, через очередь сообщений/команд. std::unique_ptr в std::function<void()> не лезет. Можно сырые указатели пересылать, но мне это не нравится.
Скажите, а вы нормальным языком постановку задачи сделать можете?
А то фразы вроде "для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей" не понятны от слова совсем.
Ну и "std::unique_ptr в std::function<void()> не лезет" так же не мешало бы расшифровать. Вы ограничены рамками C++11?
Здравствуйте, so5team, Вы писали:
S>Скажите, а вы нормальным языком постановку задачи сделать можете? S>А то фразы вроде "для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей" не понятны от слова совсем. S>Ну и "std::unique_ptr в std::function<void()> не лезет" так же не мешало бы расшифровать. Вы ограничены рамками C++11?
Здравствуйте, vopl, Вы писали:
V>С такого — противоречий нет. Оригинальный вопрос был не про противоположный полюс. V>
V>class Car;
V>class Engine;
V>class Car
V>{
V> upl::unique<Engine> engineStrong; //рассматриваем этот unique как полюс ассоциации "машина [0,1] -- [0,1] двигатель"
V> upl::weak<Engine> engineWeak; //альтернативный полюс для engineStrong. Вот про него речь. С ним проблемы.
V>};
V>class Engine
V>{
V> unspecified<Car> car; //противоположный для engineStrong+engineWeak, может быть upl::weak, а может что то другое. Не интересует
V>};
V>
Допустим, есть класс, который наблюдает за двигателем:
class Monitor
{
upl::weak<Engine> engineWeak;
}
Если двигатель внезапно удалится, то монитор должен корректно обрабатывать такую ситуацию. Если в Car будет использоваться std::unique_ptr<Engine>, то как предлагаете Monitor писать?
Здравствуйте, ViTech, Вы писали:
VT>Т.е. что происходит в этом примере
Код реализует ваш взгляд на проблему, а не описывает саму проблему. Итак, можете ли вы описать постановку задачи нормальным языком?
И что именно вы подразумеваете, когда говорите: "для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей"
VT>Хорошо, вот более короткий вариант:
Здравствуйте, so5team, Вы писали:
S>И что именно вы подразумеваете, когда говорите: "для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей"
Чуть выше пример с Car, Engine и Monitor. Например, я хочу, чтобы Car уникально владел Engine, а Monitor мог наблюдать за Engine. Как предлагаете это делать стандартными указателями С++ в многопоточной среде, чтобы избежать проблемы с dangling pointers? Или это странное желание и так делать не надо?
Здравствуйте, ViTech, Вы писали:
S>>И что именно вы подразумеваете, когда говорите: "для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей"
VT>Чуть выше пример с Car, Engine и Monitor. Например, я хочу, чтобы Car уникально владел Engine, а Monitor мог наблюдать за Engine. Как предлагаете это делать стандартными указателями С++ в многопоточной среде, чтобы избежать проблемы с dangling pointers? Или это странное желание и так делать не надо?
Самый простой способ -- это использовать std::shared_ptr. Потому, что реально у вас совместное владение объектом Engine (и в Car, и в Monitor). Поскольку Engine не может быть уничтожен раньше, чем умрут оба -- и Car и Monitor.
Более сложный -- обеспечивать контроль времени жизни Car каким-то другими средствами. Например, явно в своей программе уничтожать сперва Monitor, затем Car.
Понятно. Ты натянул устоявшуюся терминологию из одной области на другую область, это сильно сбивает с толку. Смени названия на что нибудь более близкое именно к UML-ассоциации с сотоварищами, станет намного лучше и органичнее.
Здравствуйте, so5team, Вы писали:
S>Самый простой способ -- это использовать std::shared_ptr. Потому, что реально у вас совместное владение объектом Engine (и в Car, и в Monitor). Поскольку Engine не может быть уничтожен раньше, чем умрут оба -- и Car и Monitor.
Реально у меня Monitor не владеет Engine ни в коде (upl::weak), ни в модели UML. Если помрёт Engine, то Monitor это нормально переживёт. Он не владеет Engine, лишь продлевает время его жизни на момент работы с ним, если обратился к ещё существующему объекту. А если объект уже удалился, то и при обращении к нему об этом будет известно. Можно всё через std::shared_ptr делать, если не нужно соблюдать уникальные связи. Ну и модели UML это не будет соответствовать, если в ней композитная агрегация прописана.
S>Более сложный -- обеспечивать контроль времени жизни Car каким-то другими средствами. Например, явно в своей программе уничтожать сперва Monitor, затем Car.
Хорошо бы так. Но не факт, что обеспечивать контроль жизни объектов другими средствами проще, чем изобрести weak для unique.
Здравствуйте, ViTech, Вы писали:
S>>Самый простой способ -- это использовать std::shared_ptr. Потому, что реально у вас совместное владение объектом Engine (и в Car, и в Monitor). Поскольку Engine не может быть уничтожен раньше, чем умрут оба -- и Car и Monitor.
VT>Реально у меня Monitor не владеет Engine ни в коде (upl::weak), ни в модели UML.
У вас каша в голове. Происходит это потому, что вы не можете смириться с тем, что существует разрыв между UML-моделями и особенностями их воплощения в коде на конкретном языке.
VT>Если помрёт Engine, то Monitor это нормально переживёт.
Фокус в том, что у вас и Car, и Monitor владеют Engine, вы только не находите сил себе в этом признаться. Представьте ситуацию, что на одном рабочем потоке Monitor проапгрейдил ссылку на Engine с weak на strong. А после этого на другом рабочем потоке будет уничтожен Car.
Ведь у вас Engine останется жив до тех пор, пока в Monitor не умрет string-ссылка на Engine. А это есть ни что иное, как влияние на время жизни, т.е. то самое совместное владение, наличие которого вы никак не можете признать.
И вместо того, чтобы назвать вещи своими именами вы делаете собственный велосипед, который повторяет функциональность shared_ptr, но запутывает программиста псевдо-концепциями upl::unique и upl::universal (или как они там у вас называются).
Здравствуйте, XOOIOOX, Вы писали:
XOO>Здравствуйте, kov_serg, Вы писали:
_>>Ждём Unified Math Library, Unified GUI Libaray и Unified Coroutine and Threading Library.
XOO>А так же Unified Qt Library, Unified STL Library и Unified Boost Library.
Здравствуйте, vopl, Вы писали:
V>Понятно. Ты натянул устоявшуюся терминологию из одной области на другую область, это сильно сбивает с толку. Смени названия на что нибудь более близкое именно к UML-ассоциации с сотоварищами, станет намного лучше и органичнее.
Соприкосновение областей описано в разделе Владение. Допустим, вместо upl::unique будет название upl::composite, какое название предложите для указателя, который выражает AggregationKind::none? Подозреваю, что на вопросы: "Что это за upl::composite такой?", пришлось бы отвечать: "Ну это как std::unique_ptr". И в общем не всё так просто, как может показаться на первый взгляд.
В библиотеке UPL терминология указателей используется специально, чтобы не грузить концепциями UML и было привычнее переходить/использовать совместно с умными указателями C++. Нужно только уточнить, что эти указатели выражают семантику ассоциативных связей. И могут быть, грубо говоря, как std::optional и gsl::not_null. UPL — это облегчённый набор, с минимумом зависимостей и без дебрей UML. Для дебрей UML есть отдельный проект: CppUml.
Здравствуйте, so5team, Вы писали:
S>У вас каша в голове. Происходит это потому, что вы не можете смириться с тем, что существует разрыв между UML-моделями и особенностями их воплощения в коде на конкретном языке.
Я смотрю, вы очень хорошо знаете, что у других в головах происходит .
S>Фокус в том, что у вас и Car, и Monitor владеют Engine, вы только не находите сил себе в этом признаться. Представьте ситуацию, что на одном рабочем потоке Monitor проапгрейдил ссылку на Engine с weak на strong. А после этого на другом рабочем потоке будет уничтожен Car.
S>Ведь у вас Engine останется жив до тех пор, пока в Monitor не умрет string-ссылка на Engine. А это есть ни что иное, как влияние на время жизни, т.е. то самое совместное владение, наличие которого вы никак не можете признать.
Фокус в том, что именно такое поведение мне и нужно. Чтобы во время работы с Engine в Monitor не было вылета посреди работы метода. Как вариант, можно блокировать поток, который хочет удалить объект, пока с ним работают другие потоки. Не уверен, что этот вариант лучше. Можно попытаться, перед удалением Engine в Car удалить все weak-ссылки на него, но это тоже может быть непросто. При этом, я хочу отделять уникальное владение Engine в Car от совместного. Да, я хочу писать программы в соответствии с моделями UML.
S>И вместо того, чтобы назвать вещи своими именами вы делаете собственный велосипед, который повторяет функциональность shared_ptr, но запутывает программиста псевдо-концепциями upl::unique и upl::universal (или как они там у вас называются).
Если вам хватает функциональности std::shared_ptr, и не нужны всякие псевдоконцепции, то и ладно . И будем считать, что вы меня разоблачили, и других программистов предупредили о ереси в UPL.
Здравствуйте, ViTech, Вы писали:
S>>Ведь у вас Engine останется жив до тех пор, пока в Monitor не умрет string-ссылка на Engine. А это есть ни что иное, как влияние на время жизни, т.е. то самое совместное владение, наличие которого вы никак не можете признать.
VT>Фокус в том, что именно такое поведение мне и нужно.
Фокус в том, что именно такое поведение вам и дает shared_ptr. И нет надобности в изобретении его аналогов с вводящими в заблуждение именами.
Если вы так уж хотите иметь именно "владеющий указатель", а не голый shared_ptr, то нет ничего сложного в создании обертки:
class Car : public std::enable_shared_from_this {
Engine engine_;
public:
std::weak_ptr<Engine> engine() {
return { std::shared_ptr<Engine>(shared_from_this(), &engine) };
};
};
VT>Да, я хочу писать программы в соответствии с моделями UML.
Пишите. Только сперва разберитесь каким естественным образом понятия из UML ложатся на C++, а не придумывайте велосипеды.
VT>Если вам хватает функциональности std::shared_ptr, и не нужны всякие псевдоконцепции, то и ладно
Не видно объективных причин для того, чтобы придумывать еще что-то сверх того. И вы не можете привести убедительных доводов.
Здравствуйте, ViTech, Вы писали:
VT>Здравствуйте, vopl, Вы писали:
V>>Понятно. Ты натянул устоявшуюся терминологию из одной области на другую область, это сильно сбивает с толку. Смени названия на что нибудь более близкое именно к UML-ассоциации с сотоварищами, станет намного лучше и органичнее.
VT>Соприкосновение областей описано в разделе Владение. Допустим, вместо upl::unique будет название upl::composite, какое название предложите для указателя, который выражает AggregationKind::none? Подозреваю, что на вопросы: "Что это за upl::composite такой?", пришлось бы отвечать: "Ну это как std::unique_ptr".
Да даже если оно будет называться composite (хотя наверное можно и получше имя найти) — уверен, человеку легче заранее почитать спецификацию на этот composite и сразу начать его правильно думать, чем увидев обще-употребительное наименование типа unique pointer начать его использовать с интуитивными ожиданиями а потом обнаружить что это совсем не то.
Здравствуйте, vopl, Вы писали:
V>Да даже если оно будет называться composite (хотя наверное можно и получше имя найти) — уверен, человеку легче заранее почитать спецификацию на этот composite и сразу начать его правильно думать, чем увидев обще-употребительное наименование типа unique pointer начать его использовать с интуитивными ожиданиями а потом обнаружить что это совсем не то.
Можно много всего получше сделать, поэтому хотелось бы услышать предложения по наименованиям. И что смущает в поведении upl::unique? Что объект под его управлением удаляется не сразу, а чуть погодя, когда с ним закончат работать методы объектов, которые имеют слабую ссылку на него? Он и сразу может удалиться, если с ним в текущий момент никто не работает.
Зачем bind если можно простой лямбдой ?
bind предполагает копирование, которого нет.
Что будете делать с другими некопируемыми классами ? Создавать копируемые аналоги как ваш unique ?
Здравствуйте, _NN_, Вы писали:
_NN>Зачем bind если можно простой лямбдой ? _NN>bind предполагает копирование, которого нет.
std::bind как раз копирование не предполагает, можно сделать auto some = bind(...); с перемещением в него std::unique_ptr. Но вот дальше эту some в std::function<void()> поместить не получится, именно там требуется копирование.
_NN>
Здравствуйте, ViTech, Вы писали:
V>>Да даже если оно будет называться composite (хотя наверное можно и получше имя найти) — уверен, человеку легче заранее почитать спецификацию на этот composite и сразу начать его правильно думать, чем увидев обще-употребительное наименование типа unique pointer начать его использовать с интуитивными ожиданиями а потом обнаружить что это совсем не то.
VT>Можно много всего получше сделать, поэтому хотелось бы услышать предложения по наименованиям.
К сожалению, не проникся принципами upl до глубины, поэтому вряд ли смогу генерировать эффективные наименования
VT>И что смущает в поведении upl::unique? Что объект под его управлением удаляется не сразу, а чуть погодя, когда с ним закончат работать методы объектов, которые имеют слабую ссылку на него? Он и сразу может удалиться, если с ним в текущий момент никто не работает.
Ну, тут сплошные засады Ты просто накладываешь полезняшки на чашу весов какого то своего скрытого кейса. Но надо понимать, что если ты на свою чашу что то добавил — то с другой забрал. Например, ты убил детерминированность времени жизни указываемого объекта, а существует большое число прикладных кейсов, в которых такая детерминированность нужна/полезна. Например, ты существенно увеличил расход ресурсов потому что для твоего кейса это не существенно, но для многих перерасход будет фатальным. Клоню к тому, что "унификации" не получилось. Получился какой то сугубо конкретный тул. Да еще и чужую терминологию употребил, чем сбил всех с толку, а с so5team даже подрался
Здравствуйте, ViTech, Вы писали:
VT>Эта штука, в первую очередь, нужна мне. Мне она нужна в условиях работы в многопоточной среде для организации ассоциативных связей между объектами. В частности, для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей.
Это жесть, конечно. Я всегда думал, что в UML есть три типа связей: агрегация, композиция и ассоциация (общий случай агрегации и композиции).
Здравствуйте, vopl, Вы писали:
V>Ну, тут сплошные засады Ты просто накладываешь полезняшки на чашу весов какого то своего скрытого кейса. Но надо понимать, что если ты на свою чашу что то добавил — то с другой забрал. Например, ты убил детерминированность времени жизни указываемого объекта, а существует большое число прикладных кейсов, в которых такая детерминированность нужна/полезна. Например, ты существенно увеличил расход ресурсов потому что для твоего кейса это не существенно, но для многих перерасход будет фатальным. Клоню к тому, что "унификации" не получилось. Получился какой то сугубо конкретный тул. Да еще и чужую терминологию употребил, чем сбил всех с толку, а с so5team даже подрался
Я во Введении вроде основные особенности расписал. И не говорил: "Бросайте ущербные указатели C++ и переходите на самые лучшие в мире унифицированные указатели! Без СМС и регистрации!" . Наоборот, отдельно отметил:
Указатели UPL не являются заменой умных указателей стандартной библиотеки C++ и могут использоваться совместно с ними (конечно, одновременно объект может находиться под управлением только одной библиотеки). UPL предназначена для случаев, когда не хватает функциональности умных указателей стандартной библиотеки C++ и требуются дополнительные возможности для организации связей между объектами в многопоточной среде.
Кейс тоже вроде не особо скрытый:
Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique
Кто работал со связкой std::weak_ptr <-> std::shared_ptr, по-моему, должен понять, о чём речь идёт.
По поводу детерминированности времени жизни указываемого объекта. Да, обычное поведение std::unique_ptr — это удалять объект, когда уничтожается сам указатель. И если требуется именно такое поведение, значит нужно использовать std::unique_ptr и не использовать upl::unique. Но разве в стандарте С++ говорится только о таком единственном поведении std::unique_ptr? Что объект однозначно должен быть уничтожен сразу и в том же потоке. Стандарт С++ сложно читать, и я мог пропустить такое описание, пожалуйста, покажите, если там такое есть. Я вижу, что речь там идёт о deleter, который "a function object whose correct invocation results in p’s appropriate disposition (typically its deletion).". Обязан deleter немедленно и в том же потоке удалять объект? Можно ли создать std::unique_ptr с deleter, который отложенно удалит объект, когда будет уверен, что с этим объектом никто не работает? Думаю, можно считать, что upl::unique имеет такое же поведение, как std::unique_ptr с отложенным deleter.
Накладные расходы, естественно есть, за всё надо платить. Но если вместо upl::unique использовать std::shared_ptr, чтобы получить "похожую" функциональность, то перерасход ресурсов не такой уж и большой, и зависит от реализации.
Здравствуйте, Maniacal, Вы писали:
M>Это жесть, конечно. Я всегда думал, что в UML есть три типа связей: агрегация, композиция и ассоциация (общий случай агрегации и композиции).
Одно из критических замечаний в сторону UML я слышал такое, что спецификация написана слишком общими словами, которые каждый может толковать по-своему. Единственное что могу сделать, так это попросить внимательно прочитать разделы "11.5 Associations" и "9.5 Properties" в редакции UML 2.5. Я основывался на этой редакции, и, возможно, что-то понял по-своему.
Здравствуйте, ViTech, Вы писали:
VT>По поводу детерминированности времени жизни указываемого объекта. Да, обычное поведение std::unique_ptr — это удалять объект, когда уничтожается сам указатель. И если требуется именно такое поведение, значит нужно использовать std::unique_ptr и не использовать upl::unique. Но разве в стандарте С++ говорится только о таком единственном поведении std::unique_ptr? Что объект однозначно должен быть уничтожен сразу и в том же потоке. Стандарт С++ сложно читать, и я мог пропустить такое описание, пожалуйста, покажите, если там такое есть. Я вижу, что речь там идёт о deleter, который "a function object whose correct invocation results in p’s appropriate disposition (typically its deletion).". Обязан deleter немедленно и в том же потоке удалять объект? Можно ли создать std::unique_ptr с deleter, который отложенно удалит объект, когда будет уверен, что с этим объектом никто не работает? Думаю,
Детерминизм у std::unique_ptr заключается в том что при своем разрушении он гарантированно инициирует удалятор для указываемого объекта. Это гарантия.
VT>можно считать, что upl::unique имеет такое же поведение, как std::unique_ptr с отложенным deleter.
Типа того. Минус одна гарантия. Я не говорю что это плохо, я говорю что это изменение цвета фломастера, который пробуют на вкус. Например, яваСкриптеры великолепно себя чувствуют без такой гарантии. Кому очень надо — ну, можно придумать что нибудь.
Здравствуйте, ViTech, Вы писали:
VT>Что произойдёт, когда указатель выйдет из области видимости?
Это контракт перемещения.
Используешь std::move, знай что делаешь.
Эдак можно и без перемещения написать заведомо плохой код:
Здравствуйте, ViTech, Вы писали:
V>>Ну, тут сплошные засады Ты просто накладываешь полезняшки на чашу весов какого то своего скрытого кейса. Но надо понимать, что если ты на свою чашу что то добавил — то с другой забрал. Например, ты убил детерминированность времени жизни указываемого объекта, а существует большое число прикладных кейсов, в которых такая детерминированность нужна/полезна. Например, ты существенно увеличил расход ресурсов потому что для твоего кейса это не существенно, но для многих перерасход будет фатальным. Клоню к тому, что "унификации" не получилось. Получился какой то сугубо конкретный тул. Да еще и чужую терминологию употребил, чем сбил всех с толку, а с so5team даже подрался
VT>Я во Введении вроде основные особенности расписал. И не говорил: "Бросайте ущербные указатели C++ и переходите на самые лучшие в мире унифицированные указатели! Без СМС и регистрации!" . Наоборот, отдельно отметил: VT>
VT>Указатели UPL не являются заменой умных указателей стандартной библиотеки C++ и могут использоваться совместно с ними (конечно, одновременно объект может находиться под управлением только одной библиотеки). UPL предназначена для случаев, когда не хватает функциональности умных указателей стандартной библиотеки C++ и требуются дополнительные возможности для организации связей между объектами в многопоточной среде.
VT>Кейс тоже вроде не особо скрытый: VT>
Указатель upl::weak может ссылаться на объект, который находится под управлением upl::unique
VT>Кто работал со связкой std::weak_ptr <-> std::shared_ptr, по-моему, должен понять, о чём речь идёт.
Вот, кстати, тут чуваки тоже натягивали нечто из мира UML на c++. Просто как пример наверное будет интересно
Здравствуйте, ViTech, Вы писали:
VT>Если двигатель внезапно удалится, то монитор должен корректно обрабатывать такую ситуацию. Если в Car будет использоваться std::unique_ptr<Engine>, то как предлагаете Monitor писать?
использовать std::shared_ptr<Engine> для Car и std::weak_ptr<Engine> для Monitor. В самом Engine можно обойтись либо обычным указателем, либо std::weak_ptr.
Спасибо за ссылки. В С++ из разных миров много всего натянули . Но вот потокобезопасной связки unique <-> weak (аналогичной shared <-> weak) я что-то не нашёл. Возможно плохо искал.
Здравствуйте, sergii.p, Вы писали:
VT>>Если двигатель внезапно удалится, то монитор должен корректно обрабатывать такую ситуацию. Если в Car будет использоваться std::unique_ptr<Engine>, то как предлагаете Monitor писать?
SP>использовать std::shared_ptr<Engine> для Car и std::weak_ptr<Engine> для Monitor. В самом Engine можно обойтись либо обычным указателем, либо std::weak_ptr.
Можно. Но что делать, если нужно обеспечить соблюдение требования "один Engine можно установить только в один Car"?
Здравствуйте, _NN_, Вы писали:
_NN>Здравствуйте, ViTech, Вы писали:
VT>>Что произойдёт, когда указатель выйдет из области видимости? _NN>Это контракт перемещения. _NN>Используешь std::move, знай что делаешь. _NN>Эдак можно и без перемещения написать заведомо плохой код: _NN>
Здравствуйте, ViTech, Вы писали:
SP>>использовать std::shared_ptr<Engine> для Car и std::weak_ptr<Engine> для Monitor. В самом Engine можно обойтись либо обычным указателем, либо std::weak_ptr.
VT>Можно. Но что делать, если нужно обеспечить соблюдение требования "один Engine можно установить только в один Car"?
Етить-колотить. Ну передавайте в конструктор Car экземпляр std::unique_ptr<Engine>, а внутри Car будет std::shared_ptr<Engine>.
Здравствуйте, so5team, Вы писали:
VT>>Можно. Но что делать, если нужно обеспечить соблюдение требования "один Engine можно установить только в один Car"?
S>Етить-колотить. Ну передавайте в конструктор Car экземпляр std::unique_ptr<Engine>, а внутри Car будет std::shared_ptr<Engine>.
Допустим. weak_ptr для Monitor тогда откуда брать (Car и Monitor не знают друг о друге, они знают только Engine)? И если захочется снять двигатель с одного авто и поставить в другой, как это сделать?
Здравствуйте, ViTech, Вы писали:
S>>Етить-колотить. Ну передавайте в конструктор Car экземпляр std::unique_ptr<Engine>, а внутри Car будет std::shared_ptr<Engine>.
VT>Допустим. weak_ptr для Monitor тогда откуда брать (Car и Monitor не знают друг о друге, они знают только Engine)?
Из Car. Сразу после того, как вы Car где-то в программе создали с некоторым Engine. Тут
-- простейшая обертка UniqueEngine (внутри которой shared_ptr) позволяет вам закрыть этот странный сценарий. Поскольку у вас Engine будет находиться в другом Car, но в том же самом Monitor. Что, безусловно, просто прекрасно.
И, есть ощущение, таких идиотских кейсов вы по ходу дела сможете еще кучу придумать.
A guarded pointer, QPointer<T>, behaves like a normal C++ pointer T *, except that it is automatically set to 0 when the referenced object is destroyed (unlike normal C++ pointers, which become "dangling pointers" in such cases). T must be a subclass of QObject.
А? Вместо неявного шаринга использовать такой оповещаемый не-владеющий указатель. Ну и многопоток разруливать не продлением жизни а классически, блокировками.
Здравствуйте, so5team, Вы писали:
VT>>Допустим. weak_ptr для Monitor тогда откуда брать (Car и Monitor не знают друг о друге, они знают только Engine)? S>Из Car. Сразу после того, как вы Car где-то в программе создали с некоторым Engine. Тут
-- простейшая обертка UniqueEngine (внутри которой shared_ptr) позволяет вам закрыть этот странный сценарий.
Всё бы хорошо, только из UniqueEngine::weak_ref() можно получить std::shared_ptr<Engine>, со всеми вытекающими. И UniqueEngine становится не таким уж unique.
S>Поскольку у вас Engine будет находиться в другом Car, но в том же самом Monitor. Что, безусловно, просто прекрасно.
Monitor следит за Engine, а не за Car, и я тоже считаю, что это прекрасно. Собственно, это моделью определяется.
S>И, есть ощущение, таких идиотских кейсов вы по ходу дела сможете еще кучу придумать.
Ну, правильные кейсы только у вас, кто бы сомневался .
Например, вот "кейс". Пусть каждый сам определит, насколько он идиотский.
#include <upl/pointer.h>
#include <iostream>
struct Engine
{
public:
int rpm() { return ++m_rpm; }
private:
int m_rpm{0};
};
class Car
{
public:
Car(upl::unique<Engine> engine) : m_engine{std::move(engine)} {}
void replaceEngine(upl::unique<Engine> engine)
{ m_engine = std::move(engine); }
upl::unique<Engine> releaseEngine()
{ return std::move(m_engine); }
private:
upl::unique<Engine> m_engine;
};
class Monitor
{
public:
Monitor(upl::weak<Engine> engine) : m_engine{engine} {}
void replaceEngine(upl::weak<Engine> engine)
{ m_engine = engine; }
// This method is executed in a separate thread.void activate()
{
using namespace std;
upl::unified<Engine> engine_lock = m_engine.lock();
if (engine_lock)
cout << "Engine rpm = " << (*engine_lock).rpm() << endl;
else
cout << "No engine" << endl;
}
private:
upl::weak<Engine> m_engine;
};
int main()
{
using namespace std;
upl::unique<Engine> engine_1{in_place};
upl::unique<Monitor> monitor_1{in_place, engine_1};
upl::unique<Car> car_1{in_place, move(engine_1)};
(*monitor_1).activate();
(*monitor_1).activate();
engine_1 = (*car_1).releaseEngine();
upl::unique<Monitor> monitor_2{in_place, engine_1};
upl::unique<Car> car_2{in_place, move(engine_1)};
(*monitor_1).activate();
(*monitor_2).activate();
upl::unique<Engine> engine_2{in_place};
(*monitor_2).replaceEngine(engine_2);
(*car_2).replaceEngine(move(engine_2));
(*monitor_1).activate();
(*monitor_2).activate();
return 0;
}
Здравствуйте, vopl, Вы писали:
V>А посмотри еще на QPointer: V>
V>A guarded pointer, QPointer<T>, behaves like a normal C++ pointer T *, except that it is automatically set to 0 when the referenced object is destroyed (unlike normal C++ pointers, which become "dangling pointers" in such cases). T must be a subclass of QObject.
V>Оно же, чуть ближе Smart observers to use with unique_ptr V>А? Вместо неявного шаринга использовать такой оповещаемый не-владеющий указатель. Ну и многопоток разруливать не продлением жизни а классически, блокировками.
От оповещающего невладеющего указателя в многопотоке толка мало. Объект может помереть после проверки указателя. По этой же причине мало толка от std::shared_ptr::use_count(). А std::shared_ptr::unique() deprecated in C++17 и будет removed in C++20. Нужна именно что операция lock(). Что и как там будет локироваться/блокироваться — может зависеть от реализации и требуемых вариантов использования. Кому-то нужна именно блокировка, другим — продление жизни. Но, по хорошему, алгоритм работы с указателями должен быть одинаковый. Также нужно следить, чтобы просто так нельзя было забрать владение. Чтобы была какая-то логика какое владение куда можно копировать/переместить, а куда нельзя. Желательно, чтобы всё это было с однородным интерфейсом. Средств указателей С++ мне показалось недостаточно, поэтому и начал велосипедить с UPL .
-- простейшая обертка UniqueEngine (внутри которой shared_ptr) позволяет вам закрыть этот странный сценарий.
VT>Всё бы хорошо, только из UniqueEngine::weak_ref() можно получить std::shared_ptr<Engine>, со всеми вытекающими. И UniqueEngine становится не таким уж unique.
Это напоминает анекдот: "Доктор, когда я делаю вот так, то мне больно! -- Ну и не делайте так". Откуда возьмутся эти ваши "все вытекающие?" Только в результате ваших же действий внутри Monitor. Если вы, получив из weak_ptr экземпляр shared_ptr начнете пихать его куда не попадя, то, безусловно, все вытекающие вам обеспечены.
VT>Например, вот "кейс". Пусть каждый сам определит, насколько он идиотский.
Во-первых, этот кейс с ходу реализуется за счет shared_ptr/weak_ptr из stdlib. Без каких-либо сторонних велосипедов. Да, если кто-то кривыми руками в Monitor полученный shared_ptr<Engine> начнет использовать не по делу, то по рукам не получит. Осталось только понять, стоят ли такие гарантии того, чтобы задействовать вашу библиотеку.
Во-вторых, по сути вам хочется иметь следующий набор понятий:
MasterPointer<T>. "Владеющий" умный указатель. Может быть в единственном числе. Moveable но не Copyable. Из MasterPointer-а можно получить WeakPointer.
WeakPointer<T>. Аналог std::weak_ptr, но принципиальная разница в том, что из WeakPointer можно получить только SlavePointer, но нельзя получить MasterPointer. Copyable + Moveable.
SlavePointer<T>. "Невладеющий" умный указатель. Получается из WeakPointer. Продлевает жизнь объекта. Но из SlavePointer-а нельзя получить MasterPointer. Copyable + Moveable.
С такими сущностями у вас в Car будет хранится MasterPointer<Engine>, в Monitor -- WeakPointer<Engine>, а когда Monitor-у нужно гарантировать время жизни Engine на каком-то интервале, то Monitor из WeakPointer-а получает SlavePointer<Engine>.
Причем, все эти вещи (MasterPointer, WeakPointer, SlavePointer) можно сделать очень тонкими обертками над std::shared_ptr и std::weak_ptr. Причем, думается, получится автоматически поддержать и std::enable_shared_from_this.
PS. Имя SlavePointer вряд ли удачное. Вместо него можно использовать что-то вроде TemporaryPointer или ProlongingPointer. Или вообще отказаться от этой сущности, добавив в WeakPointer метод вида:
Здравствуйте, so5team, Вы писали:
S>Во-первых, этот кейс с ходу реализуется за счет shared_ptr/weak_ptr из stdlib. Без каких-либо сторонних велосипедов. Да, если кто-то кривыми руками в Monitor полученный shared_ptr<Engine> начнет использовать не по делу, то по рукам не получит. Осталось только понять, стоят ли такие гарантии того, чтобы задействовать вашу библиотеку.
Можно. Собственно о гарантиях и речь. Руки не обязательно могут быть кривые, они могут быть невнимательные или уставшие. Типа: "Компилируйся уже и отстань от меня. Запустилось? Хорошо. Тесты прошли? Замечательно!". Это если тесты вообще есть и они что-то полезное проверяют. Поэтому хотелось бы больше гарантий от компилятора и базовых инструментов.
S>Во-вторых, по сути вам хочется иметь следующий набор понятий: S>
S> MasterPointer<T>. "Владеющий" умный указатель. Может быть в единственном числе. Moveable но не Copyable. Из MasterPointer-а можно получить WeakPointer. S> WeakPointer<T>. Аналог std::weak_ptr, но принципиальная разница в том, что из WeakPointer можно получить только SlavePointer, но нельзя получить MasterPointer. Copyable + Moveable. S> SlavePointer<T>. "Невладеющий" умный указатель. Получается из WeakPointer. Продлевает жизнь объекта. Но из SlavePointer-а нельзя получить MasterPointer. Copyable + Moveable. S>
S>С такими сущностями у вас в Car будет хранится MasterPointer<Engine>, в Monitor -- WeakPointer<Engine>, а когда Monitor-у нужно гарантировать время жизни Engine на каком-то интервале, то Monitor из WeakPointer-а получает SlavePointer<Engine>.
S>Причем, все эти вещи (MasterPointer, WeakPointer, SlavePointer) можно сделать очень тонкими обертками над std::shared_ptr и std::weak_ptr. Причем, думается, получится автоматически поддержать и std::enable_shared_from_this.
Эти все этапы я проходил. И названия свои самопальные дурацкие придумывал . И тонкие обёртки над std::shared_ptr и std::weak_ptr делал. Тут для меня ничего нового нет. Только под всем этим должна быть какая-то основа. Подходящую теоретическую основу я нашёл в UML, надо только её оптимально применить в C++, в его реалиях "не платить за то, что не используешь", и в реалиях многопоточности. Хочется проектировать, и по разработанной модели нормально программы писать, а не на обрезках и костылях. Равно как не хочется плодить новые сущности и писать велосипеды.
S>PS. Имя SlavePointer вряд ли удачное. Вместо него можно использовать что-то вроде TemporaryPointer или ProlongingPointer.
Тут эволюция была такая: Locker -> Scoped -> Unified.
S> Или вообще отказаться от этой сущности, добавив в WeakPointer метод вида: ...
Это уже дело техники. Когда будет определён необходимый набор сущностей и их операций.
По поводу примера выше. Надеюсь все представляют какие ассоциативные связи между Engine/Car/Monitor. В частности, что Car уникально владеет Engine и Monitor имеет слабую ссылку на Engine. Это нормальное желание иметь такую UML модель и корректно реализовать её в C++? И всё это работает в многопоточности. Допустим, такое уникальное владение реализовали какой-то обёрткой определённой толщины, с использованием UPL или без. Насколько нормальным является желание положить команду, например, (*car_2).replaceEngine(move(engine_2)); в очередь команд? Какими средствами проще всего организовать очередь команд?
Здравствуйте, ViTech, Вы писали:
VT>Поэтому хотелось бы больше гарантий от компилятора и базовых инструментов.
Здесь есть две проблемы.
Во-первых, ваш unique является Copyable типом. Т.е. он никакой не unique и уставшие руки могут сделать лишнюю копию unique и никто по ним не ударит. Следовательно, ваши потуги получить какие-то гарантии разрушаются вашими же проектными решениями.
Во-вторых, вы не смогли сразу внятно объяснить зачем нужна ваша библиотека. Это отличный маркер того, что вы не понимаете четко что именно вам нужно. Следовательно, стремно брать библиотеку, автор которой не отдает себе отчета в том, что и зачем он делает.
VT>Эти все этапы я проходил. И названия свои самопальные дурацкие придумывал . И тонкие обёртки над std::shared_ptr и std::weak_ptr делал. Тут для меня ничего нового нет. Только под всем этим должна быть какая-то основа. Подходящую теоретическую основу я нашёл в UML, надо только её оптимально применить в C++, в его реалиях "не платить за то, что не используешь", и в реалиях многопоточности. Хочется проектировать, и по разработанной модели нормально программы писать, а не на обрезках и костылях. Равно как не хочется плодить новые сущности и писать велосипеды.
Так вы именно это и сделали. А причина понятна: у вас каша в голове. Поэтому вы и думаете, что понятия из UML (которые не факт, что вы вообще поняли) должны естественным образом отображаться на C++. Отсюда и последствия: библиотека велосипедов, суть которых мало кому кроме вас понятна.
VT>В частности, что Car уникально владеет Engine и Monitor имеет слабую ссылку на Engine.
У вас здесь шизофрения. Если Car уникально владеет Engine, то Monitor не может воздействовать на время жизни Engine. Иначе получается никакое не уникальное, а совместное владение.
У вас же другое требование: Engine может быть установлен только в один Car. Или может принадлежать только складу. Или может находится только в одной мастерской, и то виртуально, будучи разобранным на запчасти. Это совсем другое требование. Которое не факт, что нужно выражать через систему умных указателей.
Например, вы запросто можете использовать std::shared_ptr/std::weak_ptr для хранения указателя на Engine. Но для выражения понятия владения вы можете сделать какой-нибудь OwningToken<T>. Получить этот OwningToken можно только из unique_ptr (или же порождающей функцией make_owning_token). Соответственно, из std::shared_ptr вы OwningToken не получите. Поэтому можете не беспокоится, что в Monitor из weak_ptr получается shared_ptr, а этот shared_ptr затем будет использован для обеспечения "владения" объектом.
Здравствуйте, so5team, Вы писали:
S>Здесь есть две проблемы.
S>Во-первых, ваш unique является Copyable типом. Т.е. он никакой не unique и уставшие руки могут сделать лишнюю копию unique и никто по ним не ударит. Следовательно, ваши потуги получить какие-то гарантии разрушаются вашими же проектными решениями.
С чего вы взяли, что сможете сделать копию upl::unique? Вы пробовали? Если это так, значит это ошибка и я буду её исправлять.
В остальном, я понял вашу позицию, и не хочу заходить на очередной круг обсуждения одного и того же. Спасибо за ваше мнение и попытки разобраться.
Хотелось бы услышать мнения других участников форума.
Это нужно воспринимать как перемещение. Если unified будет ссылаться на существующий unique, то, при попытке скопировать/переместить этот unified в другой unique будет выброшено исключение.
Здравствуйте, ViTech, Вы писали:
VT>Здравствуйте, vopl, Вы писали:
V>>А посмотри еще на QPointer: V>>
V>>A guarded pointer, QPointer<T>, behaves like a normal C++ pointer T *, except that it is automatically set to 0 when the referenced object is destroyed (unlike normal C++ pointers, which become "dangling pointers" in such cases). T must be a subclass of QObject.
V>>Оно же, чуть ближе Smart observers to use with unique_ptr V>>А? Вместо неявного шаринга использовать такой оповещаемый не-владеющий указатель. Ну и многопоток разруливать не продлением жизни а классически, блокировками.
VT>От оповещающегооповещаемого невладеющего указателя в многопотоке толка мало. Объект может помереть после проверки указателя.
Ну, если так в лоб, неподумавши — конечно, не срастется ничего Предполагается не "проверять-потом-использовать" а "захватывать-использовать-отпускать".
Здравствуйте, ViTech, Вы писали:
VT>Это нужно воспринимать как перемещение. Если unified будет ссылаться на существующий unique, то, при попытке скопировать/переместить этот unified в другой unique будет выброшено исключение.
Ну т.е. вы еще и проверки переносите из compile-time в run-time. Еще один большой жирный "плюс".
Здравствуйте, vopl, Вы писали:
V>>>Оно же, чуть ближе Smart observers to use with unique_ptr V>>>А? Вместо неявного шаринга использовать такой оповещаемый не-владеющий указатель. Ну и многопоток разруливать не продлением жизни а классически, блокировками.
VT>>От оповещающегооповещаемого невладеющего указателя в многопотоке толка мало. Объект может помереть после проверки указателя.
V>Ну, если так в лоб, неподумавши — конечно, не срастется ничего Предполагается не "проверять-потом-использовать" а "захватывать-использовать-отпускать".
Можно, наверное, в контрольном блоке какой-нибудь мьютекс таскать, как вариант реализации, почему нет. Если не ошибаюсь, тоже получается продление жизни объекта, только не за счёт временного захвата владения, а удержания/блокирования владения у самого владельца. И, возможно, стоит ждать в гости deadlock'и . Также, насколько знаю, блокирующим схемам не очень рады task based thread pool, типа Intel TBB.
Здравствуйте, so5team, Вы писали:
S>Здравствуйте, ViTech, Вы писали:
VT>>Это нужно воспринимать как перемещение. Если unified будет ссылаться на существующий unique, то, при попытке скопировать/переместить этот unified в другой unique будет выброшено исключение.
S>Ну т.е. вы еще и проверки переносите из compile-time в run-time. Еще один большой жирный "плюс".
Всё что можно проверить в compile-time — проверяется в compile-time. Остальное в run-time.
Здравствуйте, ViTech, Вы писали:
VT>Здравствуйте, so5team, Вы писали:
S>>Скажите, а вы нормальным языком постановку задачи сделать можете? S>>А то фразы вроде "для нормальной поддержки уникального владения (композитной агрегации), чтобы соблюдалась целостность связей" не понятны от слова совсем. S>>Ну и "std::unique_ptr в std::function<void()> не лезет" так же не мешало бы расшифровать. Вы ограничены рамками C++11?
А так не пойдет?
#include <memory>
#include <functional>
#include <iostream>
using namespace std;
void foo( shared_ptr<int> ptr )
{
cout << "foo shared: " << *ptr << endl;
}
void bar( unique_ptr<int>& ptr )
{
cout << "bar unique: " << *ptr << endl;
}
int main()
{
auto s = make_shared<int>( 7 );
function<void()> foo_f = bind( &foo, move( s ) );
foo_f();
auto u = make_unique<int>( 7 );
auto bar_f = bind( &bar, move( u ) );
bar_f();
return 0;
}
Здравствуйте, Igore, Вы писали:
I>А так не пойдет? I>
...
Так пойдет, если нужно сразу вызвать или передать Callable объект в функцию, которая получает такой объект в шаблонном параметре (например, std::thread::thread()). Но в один контейнер (std::queue, std::vector и т.п.) нельзя положить разные bind или lambda (даже с одинаковыми проблема). Такое может понадобиться, если требуется организовать очередь команд. std::function выполняет роль универсального контейнера для различных типов "команд": bind, lambda и т.п. И далее std::function с разными командами можно складывать в один контейнер (std::queue, std::vector).
Но: "std::function satisfies the requirements of CopyConstructible and CopyAssignable.". Т.е. она может копироваться, а заодно и все связанные аргументы, среди которых могут быть и некопируемые объекты. Хотя с большой вероятностью "команды" в коде будут только перемещаться, а не копироваться, но при компиляции std::function будет собираться её конструктор копирования, который будет копировать Callable объект, который некопируемый . Это одна из загвоздок, которая склонила чашу весов в сторону отдельной реализации указателей upl, основанной не на использовании std::shared_ptr/weak_ptr в качестве backend'а. Но я нашёл другой костыль для обхода этой ситуации, который позволит вернуться к реализации с использованием std::shared_ptr/weak_ptr. При этом придётся пожертвовать частью возможностей указателей upl, но реализация будет легче, и не надо будет изобретать велосипед для неё.