вопросы относительно shared_from_this

У меня есть функция, которая принимает shared_ptr<MyClass>. В какой-то функции-члене memfun из MyClass мне нужно передать this этой функции. Но если я напишу

void MyClass:memfun()
{
   func(shared_ptr<MyClass>(this))
}

Я предполагаю, что после завершения вызова счетчик ссылок достигнет 0 и будет предпринята попытка уничтожить this, что плохо.

Потом я вспомнил, что там этот класс enable_shared_from_this с функцией shared_from_this.

Итак, теперь я собираюсь использовать следующее:

class MyClass: public enable_shared_from_this<MyClass>
{
    void MyClass:memfun()
    {
       func(shared_from_this());
    }
};

Вопросы:

1) Абсолютно ли невозможно использовать функциональность без наследования от enable_shared_from_this?
2) Означает ли наследование от enable_shared_from_this, что вызов memfun для объекта с автоматическим временем хранения будет привести к чему-то плохому? Например.

 int main()
 { 
    MyClass m;   //is this OK?
    m.memfun();  // what about this?
 }

3) Если я унаследован от MyClass, будет ли функциональность enable_shared_from_this правильно унаследована или мне нужно будет унаследоваться снова? Это,

class MyCoolClass: public Myclass
{
   void someCoolMember
   {
      someCoolFuncTakingSharedPtrToMyCoolClass(shared_from_this());
   }
}

Это нормально? Или правильно следующее?

 class MyCoolClass: public Myclass, public enable_shared_from_this<MyCoolClass>
    {
       void someCoolMember
       {
          someCoolFuncTakingSharedPtrToMyCoolClass(enable_shared_from_this<MyCoolClass>::shared_from_this());
       }
    }   

Большое спасибо заранее.


person Armen Tsirunyan    schedule 08.03.2011    source источник


Ответы (5)


1) Это зависит от того, что вы подразумеваете под «сделать это» относительно того, можете ли вы это сделать. Вы всегда можете построить shared_ptr из необработанного указателя, такого как this, но он не будет делиться счетчиком ссылок с другим экземпляром shared_ptr, который был создан отдельно от необработанного указателя. Таким образом, вам нужно будет использовать настраиваемое средство удаления в том или ином экземпляре, чтобы избежать двойного удаления, но если вы не проявите большую осторожность, вы можете получить висячие экземпляры shared_ptr из-за того, что объект удаляется через один, но все еще доступен из другого.

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

2) Для вызова shared_from_this() требуется, чтобы хотя бы один экземпляр shared_ptr уже указывал на ваш объект. Если вы используете его на автоматическом объекте без экземпляра shared_ptr с пользовательским средством удаления, вы получите плохие вещи.

3) Если вы унаследованы от своего класса, то функциональность enable_shared_from_this даст вам shared_ptr базового класса (того, который получен от enable_shared_from_this). Затем вы можете использовать static_pointer_cast или dynamic_pointer_cast для приведения результата shared_from_this() к указателю на производный класс.

person Anthony Williams    schedule 27.06.2011
comment
1) ОК 2) Задокументирован ли этот факт в бусте? Я имею в виду, что мне нужен существующий объект shared_ptr? 3) Хорошо. Спасибо :) - person Armen Tsirunyan; 27.06.2011
comment
2) Да: см. boost.org/doc/libs/1_46_1 /libs/smart_ptr/ - person Anthony Williams; 27.06.2011

Важным вопросом здесь является то, почему функция принимает аргумент через shared_ptr. Сохраняет ли он указатель внутри для последующего использования? Он использует его только во время разговора? Почему право собственности разбавлено между вызывающим и вызываемым абонентами?

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

Если функция не хранит shared_ptr для последующего использования, какое дизайнерское решение привело к созданию этого API? Если вы можете изменить функцию (и для этого нет причин), сделайте так, чтобы она получала аргумент по ссылке, и у вас будет более дружественный интерфейс, который не навязывает shared_ptr без всякой причины.

Если в конце вы решите, что можете гарантировать, что объект в стеке будет жить в течение всей продолжительности процесса, запущенного этим вызовом функции, тогда и только тогда используйте удаление без операций.

person David Rodríguez - dribeas    schedule 08.03.2011
comment
примерно то, что я говорил, но вы, вероятно, написали это яснее. - person CashCow; 08.03.2011

1) Нет, это невозможно сделать без shared_from_this. Вы можете просто создать shared_ptr с помощью удаления без операций:

void do_nothing(MyClass*) {}

void MyClass:memfun()
{
    func(shared_ptr<MyClass>(this, do_nothing));
}

Ввиду того, что вам на самом деле не нужен shared_from_this, я пропущу следующие две части вашего вопроса.

person John Zwinck    schedule 08.03.2011
comment
boost уже предоставляет средство удаления без операций, хотя по какой-то причине оно скрыто в boost::serialization - person CashCow; 08.03.2011
comment
Действительно? Где? Мы хотели бы знать здесь, а также по этому вопросу: boostshared ptr">stackoverflow.com/questions/2710765/ и в системе отслеживания ошибок Boost: svn.boost.org/trac/boost/ticket/1913 - person John Zwinck; 08.03.2011
comment
shared_ptr с недействующим средством удаления Каждый раз, когда я вижу предложение использовать недействующее средство удаления, я спрашиваю: какой смысл использовать здесь умный указатель? Будет ли достаточно обычного указателя? Я не утверждаю, что любой дизайн, включающий недействующее средство удаления, абсурден, но что любой такой дизайн нуждается в конкретном обосновании. Конечно, когда использование интеллектуального указателя не может быть объяснено, вместо него следует использовать обычный. - person curiousguy; 07.10.2011
comment
Да, бывают случаи, когда вам может понадобиться создать объект на лету, для которого холдеру нужно будет удалить, когда он закончит с ним, или передать уже существующий, и в этом случае вы не хотите, чтобы пользователь удали это. Тот, кто поставляет указатель, знает, что нужно сделать с ним, когда закончите. Эта логика помещается в средство удаления. Другая сторона просто делает то, что ей говорят, и вызывает удаление, хотя и неявно. - person CashCow; 09.10.2011
comment
@CashCow Вы имеете в виду, что shared_ptr используется вызываемым абонентом, чтобы сообщить вызывающему объекту до того, как вызываемый объект вернется, что вызываемый объект больше не нужен? - person curiousguy; 09.10.2011
comment
@curiousguy Вызывающий объект предоставляет объект и знает, что он должен продолжать существовать после возврата вызываемого объекта. Вызываемый этого не знает. - person CashCow; 09.10.2011
comment
Удаление без операции — плохая идея, особенно потому, что оно почти наверняка используется, чтобы обойти семантическое обещание shared_ptr, что деструктор не будет вызываться, пока не будут выполнены все держатели. Любой клиент в стеке вызовов, который удерживает ptr за пределами вызова, скорее всего, вызовет сбой. Часто невозможно знать, что никто в цепочке вызовов не удержит его. - person Catskul; 13.02.2020

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

Это полезно для статических объектов. Если у него действительно есть локальное автоматическое хранилище, вам нужно спросить себя, почему функция использует shared_ptr. Он их хранит?

Существует еще один менее известный конструктор shared_ptr для объекта, который является членом другого объекта с подсчетом ссылок. На самом деле вы можете создать shared_ptr с shared_ptr из внешнего объекта и указателем из внутреннего объекта.

person CashCow    schedule 08.03.2011
comment
вы знаете, что время жизни вашего объекта будет достаточно продолжительным для продолжительности работы функции и что он нигде не хранит shared_ptr Тогда в чем смысл умного указателя в этом случае? - person curiousguy; 07.10.2011
comment
Функция, которую вы вызываете, требует его. Возможно, его иногда вызывают с таким, который имеет ограниченное время жизни, т.е. не гарантируется, что вызываемый будет его удерживать. - person CashCow; 09.10.2011
comment
Функция, которую вы вызываете, требует его. Я знаю, и мне интересно, почему. Возможно, его иногда вызывают с тем, который имеет ограниченное время жизни, т.е. не гарантируется, что вызываемый будет его удерживать. Я не понимаю. - person curiousguy; 09.10.2011