Является ли эта утечка памяти в std :: vector и std :: shared_ptr ошибкой? - PullRequest
0 голосов
/ 30 января 2019

Допустим, этот класс Foo:

struct Foo {
    std::shared_ptr<int> data;
    std::shared_ptr<std::vector<Foo>> foos;
};
  • имеет указатель на int

  • имеет указатель на всеэкземпляры, которые будут существовать в этой программе (следовательно, один из этих экземпляров == *this)

Давайте создадим экземпляр Foo и рассмотрим use_count() его.data переменная-член после добавления нескольких экземпляров в .foos:

int main() {
    Foo foo;
    foo.data = std::make_shared<int>(5);
    foo.foos = std::make_shared<std::vector<Foo>>();
    foo.foos->resize(8);

    for (auto & f : *foo.foos) {
        f.data = foo.data;
        f.foos = foo.foos;
    }
    std::cout << "use count: " << foo.data.use_count() << '\n';    
}

output:

use count: 9

Что нормально (1 foo + 8 .foos).Однако, похоже, что когда вернется main(), все равно будут 9 8 указателей, указывающих на .data!Это можно продемонстрировать, поместив foo в локальную область видимости и добавив один дополнительный указатель к .data для наблюдения за указателями use_count() впоследствии:

int main() {
    std::shared_ptr<int> ptr;
    std::cout << "use count | before: " << ptr.use_count() << '\n';

    { //begin scope
        Foo foo;
        foo.data = std::make_shared<int>(5);
        foo.foos = std::make_shared<std::vector<Foo>>();
        foo.foos->resize(8);

        for (auto & f : *foo.foos) {
            f.data = foo.data;
            f.foos = foo.foos;
        }
        ptr = foo.data;
        std::cout << "use count | inside: " << ptr.use_count() << '\n';

    } //end scope

    std::cout << "use count | after: " << ptr.use_count() << '\n';
}

Вывод:

use count | before: 0
use count | inside: 10
use count | after: 9

Что не хорошо.Я ожидаю, что use count | after будет 1, так как foo, и все его члены должны быть деконструированы в конце области.Хорошо, foo определенно был деконструирован (иначе use_count | after был бы 10, а не 9), но его .foos векторный указатель не был деконструирован.И ptr - это просто std::shared_ptr<int> и, следовательно, не имеет ничего общего с struct Foo.Все это можно исправить, предоставив struct Foo деструктор, который reset() s указывает .foos->data вручную:

#include <memory>
#include <iostream>
#include <vector>

struct Foo {
    ~Foo() {
        for (auto& p : *foos) {
            p.data.reset();
        }
    }

    std::shared_ptr<int> data;
    std::shared_ptr<std::vector<Foo>> foos;
};

int main() {
    std::shared_ptr<int> ptr;
    std::cout << "use count | before: " << ptr.use_count() << '\n';

    {
        Foo foo;
        foo.data = std::make_shared<int>(5);
        foo.foos = std::make_shared<std::vector<Foo>>();
        foo.foos->resize(8);

        for (auto & f : *foo.foos) {
            f.data = foo.data;
            f.foos = foo.foos;
        }
        ptr = foo.data;
        std::cout << "use count | inside: " << ptr.use_count() << '\n';
    }

    std::cout << "use count | after: " << ptr.use_count() << '\n';
}

, создавая более приятный вывод:

use count | before: 0
use count | inside: 10
use count | after: 1

Но, похоже,странно, что нужно вручную сбросить эти указатели.Почему std::vector или std::shared_ptr не делают этого автоматически здесь?Это ошибка?


Я использую Visual Studio Community 2017 версии 15.9.5 - Спасибо за любую помощь!

Ответы [ 2 ]

0 голосов
/ 30 января 2019

Вы создали циклическую зависимость: каждая Foo содержит shared_ptr с (то есть владеет долями) всех остальных Foo с.Это означает, что Foo никогда не будет разрушен: чтобы быть уничтоженным, use_count должен быть равен нулю.Но он не может быть нулем перед входом в деструктор, потому что все остальные Foo по-прежнему содержат ссылку.

Это классический случай ограничений совместного владения - вопреки некоторым убеждениям, он не решает автоматически все вашипроблемы владения.

Я бы также поставил под сомнение смысл каждого Foo, хранящего один и тот же указатель на все Foo s.Если это то, что вы хотите сделать, это должно быть static, но это тоже не похоже на хороший дизайн.Может быть, вы могли бы подробно описать реальную проблему, которую вы хотите решить (в новом вопросе)?

0 голосов
/ 30 января 2019

Проблема в том, что у вас есть круговая ссылка.

Когда foo уничтожено, оно уменьшает количество ссылок на shared_ptr, но они не достигают нуля.

Таким образом, даже если std::shared_ptr<std::vector<Foo>> "недоступен", на нем все еще есть указатель.(Примечание: сборщик мусора использует «доступность» для сбора / освобождения указателей).

Обычный метод прерывания цикла - использовать std::weak_ptr.

...