Сброс вложенного умного указателя 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 и staticinstance_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 внутренние указатели, которые указывают на 1Wrapper объект; внутренние указатели 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_ptrs, у вас есть один экземпляр содержимого объекта, т.е.

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_ptrS указывают на то же самое внутреннее 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 объект.

Другие вопросы по тегам