Должен ли enable_shared_from_this быть первым базовым классом? - PullRequest
8 голосов
/ 15 марта 2020

Мой класс наследуется от нескольких баз, одна из которых std::enable_shared_from_this. Должна ли это быть первая база?

Предположим, приведен следующий пример кода:

struct A { ~A(); };
struct B { ~B(); };
struct C : A, B, std::enable_shared_from_this<C> {};

std::make_shared<C>(); 

Когда запускаются ~A() и ~B(), могу ли я быть уверен, что хранилище, в котором жил C, проживало все еще присутствует?

Ответы [ 3 ]

5 голосов
/ 16 марта 2020

При выполнении ~ A () и ~ B () можно ли быть уверенным, что хранилище, в котором находился C, все еще присутствует?

Нет, а порядок базовые классы не имеют значения. Даже использование (или нет) enable_shared_from_this не имеет значения.

Когда объект C уничтожен (как бы это ни происходило), ~C() будет вызываться до и ~A(), а также ~B(), поскольку так работают базовые деструкторы. Если вы попытаетесь «восстановить» объект C в любом базовом деструкторе и получить к нему доступ к полям, эти поля уже будут уничтожены, поэтому вы получите неопределенное поведение.

1 голос
/ 18 марта 2020

Когда ~A() и ~B() работают, могу ли я быть уверенным, что хранилище, где жил C, все еще присутствует?

Конечно! Было бы трудно использовать базовый класс, который пытается освободить свою собственную память (память, в которой он находится). Я не уверен, что это даже формально законно.

Реализации этого не делают: когда shared_ptr<T> уничтожается или сбрасывается, счетчик ссылок (R C) для общего владельца T уменьшается (атомарно); если оно достигло 0 в декременте, то начинается уничтожение / удаление T.

Затем счетчик слабых владельцев или T-существует уменьшается (атомарно), так как T больше не существует : нам нужно знать, являемся ли мы последней сущностью, заинтересованной в блоке управления; если декремент дал ненулевой результат, это означает, что существует некоторая weak_ptr, которая разделяет (может быть 1 акция или 100%) владение контрольным блоком, и теперь они отвечают за освобождение.

В любом случае , коэффициент atomi c в какой-то момент будет иметь нулевое значение для последнего совладельца.

Здесь нет потоков, нет недетерминизма и, очевидно, последний weak_ptr<T> был уничтожен при уничтожении C. (Ваше неписанное предположение о том, что никакие другие weak_ptr<T> не были сохранены.)

Разрушение всегда происходит в этом точном порядке. Блок управления используется для уничтожения, поскольку shared_ptr<T> не знает (в общем), какой (потенциально не виртуальный) деструктор (потенциально отличающийся) наиболее производного класса для вызова . (Блок управления также знает , а не , чтобы освободить память на общем счетчике, достигнув нуля для make_shared.)

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

0 голосов
/ 16 марта 2020

Если вы создаете объект c типа C с базами A, B и счетчиком ссылок посредством наследования от базы enable_shared_from_this<T>, в первую очередь выделяется память для всего результирующего объекта, включая базы в целом и база enable_shared_from_this<T>. Объект не будет уничтожен, пока последний владелец (он же shared_ptr) не откажется от владения. В этот момент ~ enable_shared ..., ~ B и ~ A будут запущены после ~ C. Гарантируется, что заполненная память будет сохраняться до тех пор, пока не будет запущен последний деструктор ~ A. После запуска ~ A полная память объекта освобождается одним ударом oop. Итак, чтобы ответить на ваш вопрос:

Когда ~ A () и ~ B () запускаются, могу ли я быть уверен, что хранилище, где жил C, все еще присутствует?

Да, хотя вы не можете легально получить к нему доступ, но зачем вам это знать? Какую проблему вы пытаетесь избежать?

...