LINUX.ORG.RU

Запретить создание weak_ptr из shared_ptr

 


0

2

Привет, нужна помощь зала. Захотел хотелку из названия темы, подумал, что нужно отнаследоваться от std’шного и удалить operator weak_ptr

#include <memory>
using namespace std;


template <typename T>
class Shared_ptr_without_weak : public std::shared_ptr<T> {
public:
  using std::shared_ptr<T>::shared_ptr;
  operator std::weak_ptr<T>() = delete;
};

int main() {
  Shared_ptr_without_weak<int> s(new int());
  weak_ptr w0(s);  // error: use of deleted function
  weak_ptr<int> w1;
  w1 = s;          // ok
}

Результат заставил задуматься, почему в случае с конструктором задумка работает (ошибка компиляции), а с оператором= нет? В обоих случаях (ctr, operator=) shared_ptr принимается в шаблонную функцию, а версия с weak_ptr - обычная функция, нешаблонная, по идее должна иметь более высокий приоритет, но увы, во втором случае что-то идет не так.



Последнее исправление: kvpfs_2 (всего исправлений: 3)

Ответ на: комментарий от firkax

Есть, как и у конструктора, но дело в том, что версия с shared_ptr шаблонная

template< class Y >
weak_ptr& operator=( const shared_ptr<Y>& r ) noexcept;

а с weak нет

weak_ptr& operator=( const weak_ptr& r ) noexcept;
weak_ptr& operator=( weak_ptr&& r ) noexcept;

по правилам (насколько я их помню), если overload set есть подходящая (без кастов) версия, то шаблон вообще не применяется (у него ниже приоритет).

kvpfs_2
() автор топика
Ответ на: комментарий от Siborgium

Чтобы в системе не появились объекты, которые могут «утечь» из своих супервизоров-объектов через weak_ptr’ы. Что вроде удалиния shared после проверки на уникальность, а тут вдруг пояляется новый shared из weak’a. Объект удалился из менеджера, но существует. Это такое ядро, в котором юзеры исполняют свой код в плагинах, там может быть что угодно, хочу сделать защиту.

kvpfs_2
() автор топика
Ответ на: комментарий от kvpfs_2

Чтобы в системе не появились объекты, которые могут «утечь» из своих супервизоров-объектов через weak_ptr’ы.

Это такое ядро, в котором юзеры исполняют свой код в плагинах

Ну так не отдавай пользователю weak_ptr, а отдавай обертку над ним без возможности промоута до shared_ptr. /thread

Siborgium ★★★★★
()
Ответ на: комментарий от kvpfs_2

если overload set есть подходящая (без кастов) версия, то шаблон вообще не применяется (у него ниже приоритет).

И где эта подходящая версия? Она как раз в шаблоне и есть. А вариант с кастами ты удалил.

firkax ★★★★★
()
Ответ на: комментарий от Siborgium

Да это понятно, задачу я решу. Мне интересно почему подход из первого поста работает для конструктора, но не для оператора, я этого понять не могу.

kvpfs_2
() автор топика
Ответ на: комментарий от Siborgium

А вообще проблема абсолютно типичная. Из желания не дать юзеру наступить на грабли появляются усложняющие жизнь абсолютно всем приседания, которые никакого осмысленного результата не дают. Твой пользователь сделает *(volatile char*)0x0 = 0 и никакой разницы, куда там что может утечь уже не будет.

Если ты держишь пользователей за идиотов – им нельзя давать писать плагины, исполняемые нативно. Если ты не держишь их за идиотов, то не нужно делать какие-то лишние приседания, и хватит ремарки жирным в документации и коммента в API.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Ну нет, ты не прав. Компилятор конечно не должен мешать программисту делать хоть *0=0 но по крайней мере варнинг про непредусмотренную операцию, которую могли вписать случайно, неплохо было бы выдавать.

firkax ★★★★★
()

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

Если нужны смарт пойнтеры с какой-то иной схемой владения, чем в стандартных, то нужно или писать свой набор (что по сути не так уже сложно), либо использовать альтернативы. В частности, если weak pointers вообще не нужны, то я не вижу причин не использовать интрузивные указатели, это гораздо более простой и эффективный подход, чем замороченный shared_ptr

annulen ★★★★★
()
Ответ на: комментарий от kvpfs_2

а тут вдруг пояляется новый shared из weak’a. Объект удалился из менеджера, но существует.

Так не бывает, вы выдумываете функционал которого в STL нет. Weak этот тот же shared только с дополнительной проверкой на живость ссылки на объект, чтобы циклические ссылки не допускать.

raspopov
()
Ответ на: комментарий от annulen

Наследоваться нужно только от классов, спроектированных для этого

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

anonymous
()
Ответ на: комментарий от kvpfs_2

Чтобы в системе не появились объекты, которые могут «утечь» из своих супервизоров-объектов через weak_ptr’ы

… используй непрозрачные указатели, либо вообще пулы объектов из которых ничего не утечет, т.к. ничего не удаляется.

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

Твое ядро – твои правила. Просто не отдавай им никакие указатели, а предоставь апишку, полностью рулящую выделением ресурсов с условными дескрипторами, которые нельзя трактовать как указатели :)

anonymous
()
Ответ на: комментарий от anonymous

Или использовать композицию и агрегацию.

Ну как бы да, от использования композиции граблями по лицу получить сложно.

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

Использование хэндлов усложняет код, при этом нет каких-то весомых преимуществ перед использованием встроенных счётчиков ссылок (за исключением возможности передачи хэндлов между процессами, аля fd)

annulen ★★★★★
()
Ответ на: комментарий от annulen

Использование хэндлов усложняет код, при этом нет каких-то весомых преимуществ перед использованием встроенных счётчиков ссылок

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

anonymous
()

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

Наследование от классов, авторы которых не предполагали что от них будут наследоваться (большинство классов из STL не предназначены для наследования, кроме тех у которых явно в документации написано, что от них можно и нужно наследоваться, как например, std::enable_shared_from_this), сулит лютую боль тоже.

Тебе нужно использовать композицию вместо наследования, написав обёртку над shared_ptr не пробрасывая наружу методы, которые не должны быть доступны юзеру. Либо ты должен написать свою реализацию shared_ptr, там не rocket science, тем более без поддержки weak указателей реализация совсем тривиальна (разве что не забудь использовать атомики для счетчиков ссылок, если нужна потокобезопасность). И вообще, если какой-то объект всегда используется с подсчётом ссылок, то лучше сделать что-то типа boost::intrusive_ptr и хранить счётчик в самом объекте. shared_ptr слишком обобщенный и неоптимальный из-за двух указателей вместо одного.

Если прям ну очень хочешь унаследоваться, наследуйся через private, а потом делай отдельные методы public через using. Это такое себе, но хотя бы будет работать как ты хочешь.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 7)
Ответ на: комментарий от KivApple

shared_ptr слишком обобщенный и неоптимальный из-за двух указателей вместо одного.

Нормально там всё, особенно если make_shared() использовать дабы control block аллоцировался вместе в обьектом.

bugfixer ★★★★
()
Ответ на: комментарий от bugfixer

Нормально там всё, особенно если make_shared() использовать дабы control block аллоцировался вместе в обьектом.

Сам shared_ptr всё равно в два раза больше обычного указателя. В интрузивных системах такой фигни нет.

annulen ★★★★★
()
Ответ на: комментарий от bugfixer

Обычно указатели создаются и уничтожаются гораздо реже, чем копируются и муваются. Плюс с х2 размером объекта бывают проблемы у атомарных операций (size_t атомарен почти везде).

KivApple ★★★★★
()
Ответ на: комментарий от KivApple

Плюс с х2 размером объекта бывают проблемы у атомарных операций

Мы оба понимаем что в контексте shared_ptr это бред, правда? Любая многопоточная операция над конкретным instance shared_ptr требует внешней синхронизации, и понятно почему.

bugfixer ★★★★
()
Ответ на: комментарий от bugfixer

swap не требует синхронизации, так как количество ссылок в итоге не меняется. Но атомарный cmpxchg для двойного размера указателя есть далеко не на всех архитектурах.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 1)
Ответ на: комментарий от KivApple

swap не требует синхронизации

Я не до конца понимаю где Вы собрались экономить не нарушая контракта? Так же как и с COW strings - внешняя синхронизация в многопотоке Вам потребуется по-любому.

bugfixer ★★★★
()
Ответ на: комментарий от bugfixer

Она есть на уровне процессора.

Вы можете свапнуть два указателя атомарно на уровне железа.

Вы можете декрементировать счётчик ссылок и узнать что там теперь ноль (и значит никто уже не сможет его инкрементировать, вы были последним владельцем) атомарно на уровне железа.

shared_ptr, intrusive_ptr возможно реализовать без блокировок (кроме самого malloc/free). Но shared_ptr кое где не умеет в swap без блокировок из-за двойного размера. А на swap строятся некоторые lock free паттерны.

KivApple ★★★★★
()
Последнее исправление: KivApple (всего исправлений: 3)
Ответ на: комментарий от raspopov

Я ничего не придумываю, это не функционал СТЛ, а моя логика. Один поток содержит массив с шаред_птрами, может удалять их проверив, что он владеет эксклюзивно, и если другой поток припрятал weak_ptr
и конструирует из него shared в промежутке между проверкой на уникальность и удалением из массива, то имеем утекший объект. Не в смысле там памяти, а ушедший из под надзора своего менеджера.

В общем-то до меня дошло как удалить корректно (без вероятности утечек):

vector<shared_ptr> v{...};
// try to delete back element
weak_ptr w = v.back();
v.pop_back()
if (! w.expired()) {
   v.push_back(shared_ptr(w);
   return false; // error, not deleted, busy
}
return true; // ok

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

kvpfs_2
() автор топика
Последнее исправление: kvpfs_2 (всего исправлений: 1)
Ответ на: комментарий от anonymous

с условными хэндлами, единственно доступными юзерам.

Это раздувает код (+ замедляет), так надо дублировать все методы объекта в его менеджере + обращение постоянно идет через этот менеджер а не напрямую (с поиском элемента, который за этим дескриптором). В системе и так есть хэндлы, но я осознанно не организую работу через них (они только как ID для добавить/вставить/подписать объект на что-нибудь из панели управления).

kvpfs_2
() автор топика
Последнее исправление: kvpfs_2 (всего исправлений: 1)
Ответ на: комментарий от kvpfs_2

Это раздувает код (+ замедляет), так надо дублировать все методы объекта в его менеджере + обращение постоянно идет через этот менеджер а не напрямую (с поиском элемента, который за этим дескриптором)

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

обращение постоянно идет через этот менеджер а не напрямую (с поиском элемента, который за этим дескриптором)

ну да ну да, нужно же каждый раз искать, кэшировать же религия запрещает :) А так же с самого начала передавать все критичные к скорости запчасти в подсистему, где не надо «постоянно обращаться через менеджер», т.к. все и так рядом.

anonymous
()
Ответ на: комментарий от kvpfs_2

Один поток содержит массив с шаред_птрами, может удалять их проверив, что он владеет эксклюзивно

Вот в этом ваша проблема - с точным пониманием что есть что в программе.

Поток НЕ владеет shared_ptr (массив не владеет ими тоже), и удалить он их не может, а может только освободить, тем самым уменьшив счётчик в shared_ptr, и уже затем сам shared_ptr решит пора удалять объект которым он владеет или нет, ну и напоследок убьёт себя. И про weak_ptr лучше думать так же, с разницей, что он не препятствует самоуничтожению родительского shared_ptr.

raspopov
()
Ответ на: комментарий от kvpfs_2

https://0x0.st/Hgzr.png

И всё же я посоветую вам пересмотреть компоновку классов в следующем ключе:

class plugin1 {};

class manager
{
public:
	void my_last_will(plugin1 * p) {};
};

manager _manager;

class plugin2 : public plugin1
{
public:
	~plugin2() 
	{
		_manager.my_last_will( this );
	}
};

void deleter(plugin1 * p)
{
	_manager.my_last_will( p );
	delete p;
}

std::shared_ptr< plugin1 > v1( new plugin1, &deleter );

std::shared_ptr< plugin2 > v2( new plugin2 );

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

raspopov
()
Ответ на: комментарий от raspopov

Спасибо, но это работать не будет, всё несколько сложнее. Картина примерно такая: есть объекты А и менджер этих объектов А, есть объекты Б и менджер объектов Б. Объекты Б могут подписывать на объекты А (тем самым оставлять на хранение в них свой shared_ptr, от weak_ptr отказался). Также шаред_птр Б хранится в менеджере Б всегда, даже если не осталось левых ссылок в каком-то юзерском коде, например. Единственный путь удалить Б - отправить соответствующий запрос менеджеру Б, он должен решить - можно ли удалить данный шаред_птр или нельзя и нужно и дальше держать свой указатель, при этом он должен учесть, что ещё один шаред_птр хранится у объекта на который подписан Б. Ещё добавьте в это всё многопоточность и всякие сюрпризы с нею связанные - если, например, сначала отписаться, а потом решать что делать, то возможны косяки - менеджер решил, что удалять нельзя, вернул всё обратно, но за время отсутствия подписки пропустил событие, это недопустимо, возможны и другие сценарии.

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

kvpfs_2
() автор топика
Ответ на: комментарий от kvpfs_2

он должен решить - можно ли удалить данный шаред_птр или нельзя и нужно и дальше держать свой указатель, при этом он должен учесть, что ещё один шаред_птр хранится у объекта на который подписан Б.

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

Ещё добавьте в это всё многопоточность

Косяков многопоточности с shared_ptr не бывает, если использовать только их и не выковыривать неуправляемый указатель из них.

Данный вопрос я уже решил.

Не бойтесь рефакторить свой код, не нужно рефакторить только уже мёртвые программы. 😎

raspopov
()
Ответ на: комментарий от raspopov

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

Вначале так и хотел делать, а потом подумал, что можно не кастовать постоянно weak->shared при каждом приходе события в объект А с «проталкиванием» его подписчикам Б. Может я на спичках сэкономил и делать shared из weak не так уж дорого, если често, то я не проверял. В общем-то для меня это все равно не сложно, в Менеджере Б кастую шаред в вик, дергаю в А delete_if_expired(), он делает тоже самое и если не expired то возвращает шаред на место и рапортует в Б о неудаче, Б при неудаче поступает аналогично. В общем всё получается без всяких провалов в цепи событий.

Косяков многопоточности с shared_ptr не бывает, если использовать только их и не выковыривать неуправляемый указатель из них.

Зато полно всяких других, один лишь упомянутый уже мной запуск/остановка - тот ещё хай-тек, в общем-то типичная задача для многопоточной софтины, наверное, когда нужно сохранить состояние через перезапуск. Я к этому проекту возвращаюсь после некоторого перерыва, скажу честно - в прошлый раз я эту задачу не осилил (забил поломав мозг N времени).

kvpfs_2
() автор топика