Почему вызов shared_from_this вызывает std :: terminate - PullRequest
0 голосов
/ 10 мая 2018

Рассмотрим этот код:

class A : public std::enable_shared_from_this<A>
{
public:
     std::shared_ptr<A> f()
     {
          return shared_from_this();
     }
};

int main()
{
    A a;
    std::shared_ptr<A> ptr = a.f();
}

Этот код завершен в Visual Studio 2017. Думаю, я что-то здесь не так делаю. Кто-нибудь может мне с этим помочь? Я хочу, чтобы shared_ptr был создан shared_from_this ().

Ответы [ 3 ]

0 голосов
/ 10 мая 2018

Поскольку a не принадлежит совместно используемому указателю. От cppreference :

Разрешается вызывать shared_from_this только для ранее общего объекта, т.е. для объекта, управляемого std :: shared_ptr. В противном случае поведение не определено (до C ++ 17), генерируется std :: bad_weak_ptr (с помощью конструктора shared_ptr, созданного по умолчанию weak_this) (начиная с C ++ 17).

0 голосов
/ 12 мая 2018

Проблема принципиальна в дизайне (не технические детали)

Независимо от точной спецификации используемой вами стандартной версии C ++, то, что вы пытаетесь сделать, невозможно. Знание мелких деталей спецификации shared_from_this не является необходимым для заключения о том, что код содержит противоречие проекта: просто понимание намерения, которое заключается в получении от shared_ptr<A> до this внутри функции-члена вызов a, автоматического объекта, достаточен для определения ошибки проекта.

Фактически, любая попытка создания умного указателя (включая, но не ограничиваясь unique_ptr, shared_ptr), который указывает на (который владеет) объект с временем жизни "scoped", это объект, время жизни которого определяется областью объявления объекта и заканчивается завершением чего-либо:

  • автоматические объекты (время жизни заканчивается при выходе из области видимости),
  • объекты области пространства имен, члены статических объектов классов (время жизни заканчивается при выходе из программы),
  • нестатические члены классов (время жизни заканчивается, когда выходит тело деструктора содержащего объекта класса),

является ошибкой проектирования, потому что:

  • эти объекты не были созданы ни с одним вариантом new (обычный operator new или nothrow вариант), который разрешает вызов delete для результата);
  • единственный случай, когда C ++ позволяет уничтожить такой объект, - это уничтожение, за которым (желательно сразу) следует реконструкция с размещением нового объекта с таким же полным типом, что, очевидно, не является задачей обладателя умного указателя;
  • компилятор уничтожит этот объект, когда выполнение программы достигнет точки выхода (выходит из области действия, выходит из деструктора, или std::exit, или return из main), независимо от того, что (даже если ваш умный указатель уже позаботился о Это); пытаться уничтожить уже уничтоженный объект не в порядке.

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

struct A {
    int m;
};

#define OK 1

void f() {
    A *p = new A;
#if OK            
    std::shared_ptr<A> own (p); // fine
#else
    std::shared_ptr<int> own (&p->m); // bad
#endif
}

Здесь время жизни объекта, на которое указывает p, управляется динамически; время уничтожения, определенное явно программным кодом, и время жизни уникального члена m неразрывно связаны со временем жизни объекта A; но сам член не должен быть уничтожен явно и не должен быть удален. Если константа препроцессора OK равна 1, все в порядке; если оно равно 0, вы пытаетесь явно управлять временем жизни члена, что несостоятельно.

О термине «явный» вызов delete: хотя оператор delete никогда не появляется в коде, его вызов подразумевается при использовании std::shared_ptr; другими словами, std::shared_ptr явно использует delete, поэтому использование std::shared_ptr (или других аналогичных интеллектуальных указателей, владеющих) является косвенным использованием delete.

Безопасное разделение прав владения с помощью умного указателя

Единственный безопасный способ поделиться правом собственности на shared_ptr - это сделать одно из другого shared_ptr, прямо или косвенно. Это фундаментальное свойство shared_ptr: все экземпляры, указывающие на один объект должен прослеживаться до одного экземпляра, созданного с помощью необработанного указателя (или, альтернативно, с make_shared).

Это прямое следствие того факта, что информация о владельце (обычно счетчик ссылок, но может быть связанным списком, если вы любите неэффективные реализации) - это не внутри управляемого объекта, но внутри информации Блок создан shared_ptr. Это не свойство просто std::shared_ptr, это факт жизни всех этих управляемых извне объектов, без глобального реестра невозможно найти менеджера.

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

Важность слабых копий общего владельца-менеджера

Фундаментальное свойство shared_ptr может создать проблему: поскольку каждый слой кода (которому может потребоваться вызвать функцию, для которой требуется указатель-владелец) должен хранить копию shared_ptr, это может создать сеть владения умными указателями, некоторые из которых могут находиться в объекте, временем жизни которого управляет другой, а временем жизни управляет этот точный умный указатель; поскольку базовая спецификация интеллектуального указателя заключается в том, что управляемый объект не уничтожается до уничтожения всех копий интеллектуального указателя, ответственного за его уничтожение, эти объекты никогда не будут уничтожены (как указано; это не следствие конкретной реализации выбора подсчета ссылок). Иногда требуется копия «умного» указателя-владельца, которая не препятствует влиянию времени жизни управляемого объекта, а значит, и слабого «умного» указателя.

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

Единственная цель слабого умного указателя - получить такие копии оригинала .

Цель std::enable_shared_from_this

Единственное использование std::enable_shared_from_this - получить копию оригинала shared_ptr; это означает, что такой умный указатель должен уже существовать . Новый оригинал (еще один умный указатель, вступающий во владение) не будет.

Используйте std::enable_shared_from_this только для классов, которые предназначены только для управления shared_ptr.

Подробная информация о std::enable_shared_from_this

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

«Волшебство» std::enable_shared_from_this может показаться загадочным и слишком волшебным, чтобы пользователям не приходилось об этом думать, но на самом деле это очень просто: он хранит weak_ptr, предназначенный для копирования оригинал . Очевидно, что он не может быть создан в качестве такой копии, поскольку исходный не может быть даже инициализирован при создании подобъекта std::enable_shared_from_this: действительный умный указатель-владелец может ссылаться только на полностью построенный объект, поскольку он владеет им и отвечает за его уничтожение. [Даже если путем некоторого обмана был создан интеллектуальный указатель-владелец до того, как управляемый объект был полностью создан и, следовательно, разрушаем, интеллектуальный указатель-владелец может привести к преждевременному разрушению (даже если при нормальном ходе событий его время жизни велико, он может быть сокращен, например, исключением). *

Таким образом, инициализация элемента данных в std::enable_shared_from_this изначально является инициализацией по умолчанию: в этой точке «слабый указатель» равен нулю.

Только когда исходный наконец вступит во владение управляемым объектом, он может вступить в сговор с std::enable_shared_from_this: создание исходного shared_ptr будет установить раз и навсегда элемент weak_ptr внутри std::enable_shared_from_this. Активный сговор между этими компонентами - единственный способ заставить вещи работать.

Пользователь по-прежнему обязан вызывать shared_from_this только тогда, когда он может вернуть копию оригинала , то есть после оригинала был создан.

О поддельных (не владеющих) владельцах умных указателей

Фальшивый владелец умного указателя - это тот, который никогда не очищается: владелец умного указателя только по имени . Они являются особым случаем «владения» умными указателями, используемыми таким образом, что не выполняется никакого уничтожения или очистки. Это якобы означает, что они могут быть использованы для объектов, чье время жизни предопределено (и достаточно долго) и для которых необходимо иметь умный указатель с притворством; в отличие от настоящего интеллектуального указателя, сохраняющего копию, срок службы объекта не продлевается, поэтому срок службы должен быть действительно длинным. (Поскольку копия «умного» указателя-владельца может храниться в глобальной переменной, можно ожидать, что объект все еще будет живым после return из main.)

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

Они редко решают законную проблему (которая не является непосредственным следствием очень плохого дизайна): shared_ptr в интерфейсе означает, что получатель рассчитывает продлить срок службы управляемого объекта.

0 голосов
/ 10 мая 2018

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

std::shared_ptr a(new A());
auto ptr = a->f(); // ok, 'ptr' shares ownership of newed object with 'a'
...