std::make_shared(), std::weak_ptr и циклические ссылки

Мой вопрос касается этого утверждения:

Если какой-либо std::weak_ptr ссылается на управляющий блок, созданный std::make_shared после окончания времени существования всех общих владельцев, память, занимаемая T, сохраняется до тех пор, пока все слабые владельцы также не будут уничтожены, что может быть нежелательно, если sizeof(T) равен большой. Источник

Я прочитал здесь, что этот объект живет до тех пор, пока не появится последний weak_ptr. Освобождает ли он объект, созданный с помощью make_shared, с циклической ссылкой на себя или он будет жить в памяти вечно?

Например:

struct A
{
    std::weak_ptr<A> parent;
}

void fn()
{
    auto a=std::make_shared<A>();
    a->parent = a;
} // Will it destroy here or not?

person Vizor    schedule 28.06.2017    source источник


Ответы (3)


Он разрушен. Это одна из причин существования weak_ptr.

Когда a уничтожается, счетчик ссылок становится равным 0, поэтому объект уничтожается. Это означает, что вызывается деструктор объекта, который также уничтожает a->parent.

Не путайте уничтожение с освобождением. Когда счетчик ссылок становится равным 0 или объект не принадлежит shared_ptr, объект уничтожается. Если есть какой-либо weak_ptr, указывающий на управляющий блок, память не будет освобождена, поскольку объект был выделен с помощью std::make_shared, но объект определенно уничтожен.

person ikh    schedule 28.06.2017
comment
Гораздо понятнее, чем мое объяснение, и +1 за первое предложение последнего абзаца. - person Martin Bonner supports Monica; 28.06.2017

Проблема связана с:

Если какой-либо std::weak_ptr ссылается на управляющий блок, созданный std::make_shared после окончания времени существования всех общих владельцев, память, занимаемая T, сохраняется до тех пор, пока все слабые владельцы также не будут уничтожены, что может быть нежелательно, если sizeof(T) равен большой

что-то вроде

std::weak_ptr<A> global;

void foo()
{
    auto a = std::make_shared<A>();
    global = a;
}

Итак, global указывает на блок управления a. Здесь, даже если a уничтожен, память, используемая a, все еще присутствует, чтобы позволить блоку управления существовать.

person Jarod42    schedule 28.06.2017

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

Конечно, ожидается, что std::make_shared сведет к минимуму количество динамических распределений памяти путем объединения метаинформации и управляемого объекта, но это не имеет никакого отношения к тому, когда управляемый объект будет уничтожен (что является единственным наблюдаемым аспектом, поскольку нет специфичных для класса operator new/operator delete). был использован). Таким образом, не имеет значения, находится ли управляющий блок вместе с управляемым объектом или имеет указатель на этот объект, выделенный отдельно.

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

  • формы delete p; для запуска, где p — аргумент типа T* конструктора std::shared_ptr<U>,
  • или формы p->~T(), где p является результатом new (buff) T в std::make_shared<T>().

В любом случае значение p можно получить из метаинформации.

[Обратите внимание, что удаляемое значение p никогда не получается из значения указателя U*, хранящегося в каком-либо конкретном экземпляре std::shared_ptr<U>, поскольку такое значение указателя не может быть "удаляемым", поскольку деструктор не может быть виртуальным (пока аргумент указателя std::shared_ptr<U> имеет правильный статический тип: достаточно, чтобы delete p;, где p — значение типа, переданного шаблонному конструктору), поскольку указатель может относиться к подобъекту-члену или полному другому завершенному объекту, если конструктор псевдонимов использовался для создания другого std::shared_ptr с общей собственностью.]

Таким образом, наличие указателя на управляющий блок обычно позволяет восстановить указатель на контролируемый объект, хотя этот указатель на весь объект нельзя получить через общедоступный интерфейс (за исключением того, что указатель передается удаляющему, поэтому единственный способ восстановить указатель в С++, если он был утерян, нужно было передать пользовательский модуль удаления и дождаться его вызова). Указатель, безусловно, может быть восстановлен путем навигации внутри представления памяти (хотя для этой навигации может потребоваться использовать dynamic_cast для типа, неизвестного во время компиляции, что-то, что отладчик сможет сделать, если он знает обо всех производных классах).

Итак, у нас есть цикл:

a
a->parent
   parent->control_block
           control_block.deleter (virtual call or stored function)
                         deleter.a

если указатель хранится в динамически созданном средстве удаления, что необходимо для создания std::shared_ptr<U>(T*), или

a
a->parent
   parent->control_block
           control_block.buffer

для объектов, созданных с помощью одного выделения make_shared: объект был создан внутри этого буфера, поэтому &control_block.buffer == a.

Но циклы указателей - это не проблема, а только цикл владения, поскольку он подразумевает «владение собой, контролируемое временем жизни», то есть «я уничтожу себя только тогда, когда моя жизнь закончится» (иначе «я войду в деструктор, когда у меня будет введен деструктор"), абсурд.

Здесь нет собственности, поскольку слабая ссылка владеет только метаинформацией, а не самой информацией.

person curiousguy    schedule 13.06.2018