Сброс вложенного умного указателя shared_ptr на shared_ptr (или unique_ptr), кажущийся парадоксом
Я знаю, что объект управляется std::shared_ptr
не является delete
д reset()
если это не единственный shared_ptr
который управляет этим в тот момент. Я знаю, что когда есть несколько shared_ptr
При управлении одним и тем же объектом изменения значения управляемого объекта отражаются во всех shared_ptr
что указывают на это, в то время как изменения в любой из этих shared_ptr
значение (не значение его управляемого объекта), вызванное reset()
(то есть изменение shared_ptr
от того, который указывает на исходный управляемый объект, к тому, который указывает на ничто или что-то еще), не меняет другой shared_ptr
Значения s (т. е. все они по-прежнему указывают на исходный управляемый объект, а исходный управляемый объект все еще существует):
#include <memory>
#include <vector>
#include <iostream>
using namespace std;
int main() {
vector<shared_ptr<int>> vec{ make_shared<int>(5) };
shared_ptr<int> sptr(vec[0]);
++ *sptr;
cout << *vec[0] << endl; // 6
vec[0].reset();
vec.pop_back();
cout << *sptr << endl; // 6
}
Но эта логика теряется для меня при использовании двух уровней косвенности. Учитывая класс с именем Wrapper
и shared_ptr<shared_ptr<Wrapper>>
и любое количество других shared_ptr<shared_ptr<Wrapper>>
инициализирован до предыдущего, почему эта конфигурация позволяет reset()
называется на любой внутренний shared_ptr
эффективно reset()
все остальное внутреннее shared_ptr
s?
Мое предположение: управляемый объект любого из shared_ptr
с является внутренним shared_ptr
(не Wrapper
) и изменения в стоимости внутреннего shared_ptr
(от reset()
тинг внутренний shared_ptr
, который меняет значение внутреннего shared_ptr
от того, который указывает на Wrapper
экземпляр, который указывает на ничто) отражается во всем shared_ptr
с, эффективно вызывая все shared_ptr
s потерять косвенное управление над Wrapper
экземпляр, тем самым удаляя Wrapper
пример.
Но по той же логике, не сбрасывает ли один из внутренних указателей только собирается вызвать этот конкретный внутренний указатель потерять управление над Wrapper
? Учитывая, что все другие внешние указатели указывают на свои собственные внутренние указатели (то есть те, которые были созданы с ними), не будут ли эти внешние по-прежнему иметь косвенное управление над Wrapper
, так как сброс одного внутреннего указателя не меняет Wrapper
значение, которое все еще должно быть доступно другим внутренним указателям? Это парадокс для меня.
Если сброс одного внутреннего указателя эффективно сбрасывает все из них, то это означает, что внутренние указатели use_count()
было 1
прямо перед reset()
, Единственный способ, которым я думал несколько shared_ptr
s могут управлять одним и тем же объектом, сохраняя use_count()
в 1
было бы через иллюзию: они управляют разными объектами (то есть объектами по разным адресам), которые имеют одинаковое значение. Я проверил это, сделав int
оболочка с именем Wrapper
, чьи единственные члены данных являются завернутыми int
и static
instance_count
который отслеживает количество Wrapper
экземпляры, которые существуют в настоящее время.
struct Wrapper {
Wrapper(int par = 0) : num(par) { ++instance_count; }
Wrapper(const Wrapper& src) : num(src.num) { ++instance_count; }
~Wrapper() { --instance_count; }
int num;
static int instance_count;
};
int Wrapper::instance_count = 0;
int main() {
shared_ptr<shared_ptr<Wrapper>> dual_ptr_1(
make_shared<shared_ptr<Wrapper>>(
make_shared<Wrapper>(Wrapper(5))
)
);
// - Output -
cout << Wrapper::instance_count << endl; // 1
shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(dual_ptr_1);
cout << Wrapper::instance_count << endl; // 1
cout << dual_ptr_1->use_count() << endl; // 1
cout << dual_ptr_2->use_count() << endl; // 1
cout << dual_ptr_1.use_count() << endl; // 2
cout << dual_ptr_2.use_count() << endl; // 2
// note that above, the '->' operator accesses
// inner ptr while '.' operator is for outer ptr
cout << (*dual_ptr_1)->num << endl; // 5
cout << (*dual_ptr_2)->num << endl; // 5
dual_ptr_2->reset();
cout << Wrapper::instance_count << endl; // 0
cout << dual_ptr_1->use_count() << endl; // 0
cout << dual_ptr_2->use_count() << endl; // 0
cout << dual_ptr_1.use_count() << endl; // 2
cout << dual_ptr_2.use_count() << endl; // 2
}
Видимо были 2
внутренние указатели, которые указывают на 1
Wrapper
объект; внутренние указатели use_count
было максимум 1
(до уничтожения); Wrapper
класса instance_count
было максимум 1
(до уничтожения); и косвенно управляемый Wrapper
объект был доступен через оба внешних указателя (что означает, что ни один внешний указатель не был создан другим); и сброс одного внутреннего указателя эффективно сбрасывает их все; так что я до сих пор не понимаю кажущийся парадокс.
Я также задаю те же вопросы в этом посте о случае, в котором приведенный выше код имеет внутренний shared_ptr
с заменены на unique_ptr
с, внутренний make_shared
заменен на make_unique
и use_count()
закомментированы для внутренних указателей (потому что unique_ptr
не хватает этого метода), который дает тот же результат. Это кажущийся парадокс для меня, потому что unique_ptr
здесь не кажется уникальным.
2 ответа
Учитывая класс с именем
Wrapper
иshared_ptr<shared_ptr<Wrapper>>
и любое количество другихshared_ptr<shared_ptr<Wrapper>>
инициализирован до предыдущего, почему эта конфигурация позволяетreset()
вызывается на любом внутреннем shared_ptr для эффективногоreset()
все остальное внутреннееshared_ptrs
?
Там нет другого внутреннего shared_ptr
s, у вас есть один экземпляр содержимого объекта, т.е.
dual_ptr_1
\
--> shared_ptr --> Wrapper
/
dual_ptr_2
И не
dual_ptr_1 --> shared_ptr
\
--> Wrapper
/
dual_ptr_2 --> shared_ptr
После вашего звонка dual_ptr_2->reset();
это меняется на
dual_ptr_1
\
--> shared_ptr --> (empty)
/
dual_ptr_2
Принятый ответ показывает на диаграмме, что происходит в коде OP: два внешних shared_ptr
S указывают на то же самое внутреннее shared_ptr
, который указывает на Wrapper
объект. (Я ссылаюсь на диаграммы в неотредактированной версии принятого ответа; он не был отредактирован на момент моего ответа здесь.) Принятый ответ имеет другую диаграмму, которая показывает, что ОП ожидало, но не произошло, который я называю как:
Случай А - два внешних указателя, которые указывают на разные внутренние указатели, которые указывают на один и тот же Wrapper
объект (см. принятый ответ для диаграммы).
Вот код, который вызывает случай А:
int main() {
shared_ptr<shared_ptr<Wrapper>> dual_ptr_1(
make_shared<shared_ptr<Wrapper>>(make_shared<Wrapper>(5))
);
shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(
make_shared<shared_ptr<Wrapper>>(*dual_ptr_1)
);
cout << dual_ptr_1.use_count() << endl; // 1
cout << dual_ptr_2.use_count() << endl; // 1
cout << dual_ptr_1->use_count() << endl; // 2
cout << dual_ptr_2->use_count() << endl; // 2
}
Я имею в виду dual_ptr_1
в качестве первого внешнего указателя и shared_ptr
на что он указывает в качестве первого внутреннего указателя. Я имею в виду dual_ptr_2
в качестве второго внешнего указателя и shared_ptr
на что он указывает как второй внутренний указатель. Два внешних указателя указывают на разные внутренние указатели. Второй внешний указатель не был сконструирован для копирования или назначен первому внешнему указателю, поэтому либо внешний указатель use_count
является 1
, The second outer pointer does not point to the first inner pointer, but rather a nameless inner pointer that's copy-constructed from the first inner pointer. While the second outer pointer is still managing the second inner pointer, the latter's namelessness doesn't cause the latter to go out of scope. The second inner pointer points to the same Wrapper
as the first inner pointer, because the second inner pointer was copy-constructed from the first inner pointer. Из-за этого shared_ptr
copy-construction, either inner pointer's use_count
является 2
, Each inner pointer must be reset()
to nothing or something else, assigned to something else, or go out of scope in order for the Wrapper
to be destroyed (the inner pointers don't both need to go through the same operation as long as each one goes through at least one of them).
Here's another one, Case B – same diagram as Case A, but with a buggy implementation and different console output:
int main() {
shared_ptr<shared_ptr<Wrapper>> dual_ptr_1(
make_shared<shared_ptr<Wrapper>>(make_shared<Wrapper>(5))
);
shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(
make_shared<shared_ptr<Wrapper>>(&(**dual_ptr_1))
); // (*)
cout << dual_ptr_1.use_count() << endl; // 1
cout << dual_ptr_2.use_count() << endl; // 1
cout << dual_ptr_1->use_count() << endl; // 1
cout << dual_ptr_2->use_count() << endl; // 1
} // <- Double free runtime error at closing brace.
// Replacing line (*) with:
// shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(
// new shared_ptr<Wrapper>(&(**dual_ptr_1))
// );
// has much the same effect, possibly just without compiler optimization.
Case B is a buggy variation of Case A, where the difference in Case B is that the second inner pointer is constructed from a raw pointer to the Wrapper
object rather than copy-constructed or assigned from the first inner pointer. Because of this, either inner pointer's use_count
остается в 1
(вместо 2
) even though they both point to the same address. So, each of these inner pointers behave as if it is the only one that manages the Wrapper
объект. A double free runtime error occurs at main()
's closing brace, because the last inner pointer to go out of scope is trying to free memory already freed by the previous inner pointer's going out of scope.
Here's Case C - two outer pointers point to different inner pointers that point to different Wrapper
объекты:
int main() {
shared_ptr<shared_ptr<Wrapper>> dual_ptr_1(
make_shared<shared_ptr<Wrapper>>(make_shared<Wrapper>(5))
);
shared_ptr<shared_ptr<Wrapper>> dual_ptr_2(
make_shared<shared_ptr<Wrapper>>(make_shared<Wrapper>(**dual_ptr_1))
);
cout << dual_ptr_1.use_count() << endl; // 1
cout << dual_ptr_2.use_count() << endl; // 1
cout << dual_ptr_1->use_count() << endl; // 1
cout << dual_ptr_2->use_count() << endl; // 1
}
Хотя Wrapper
objects have the same value, they are different objects and they are at different addresses. The second inner pointer's Wrapper
object was copy constructed from the first inner pointer's Wrapper
объект.