简体   繁体   中英

Is this std::vector and std::shared_ptr memory leakage a bug?

Assume this class Foo :

struct Foo {
    std::shared_ptr<int> data;
    std::shared_ptr<std::vector<Foo>> foos;
};
  • it has a pointer to an int

  • it has a pointer to all instances that will exist in this program (therefore one of these instances == *this )

Let's create a instance of Foo and take a look at the use_count() of its .data member variable after having added some instances to .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

Which is fine (1 foo + 8 .foos ). However, it seem that when main() returns, there will still be 9 8 pointers pointing to .data ! This can be demonstrated by putting foo into a local scope and letting one additional pointer point to .data to observe this pointers use_count() afterwards:

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';
}

The output is:

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

Which is not good. I would excpect use count | after use count | after to be 1 since foo and all its members should get deconstructed at the end of the scope. Well, foo definetely got deconstructed (otherwise use_count | after would be 10 and not 9 ) but its .foos vector pointer weren't deconstructed. And ptr is just a std::shared_ptr<int> and therefore has nothing to do with struct Foo at all. All this can be fixed by providing struct Foo a destructor which reset() s the .foos->data pointer manually:

#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';
}

producing the nicer output:

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

But it seem weird that one has to manually reset these pointers. Why do std::vector or std::shared_ptr not do that automatically here? Is it a bug?


I am using Visual Studio Community 2017 Version 15.9.5 - Thanks for any help!

Problem is that you have a circular reference.

When foo is destroyed, it decreases reference count of its shared_ptr , but those don't reach zero.

So even if std::shared_ptr<std::vector<Foo>> is "inaccessible" , there are still pointer on it. (Note: Garbage collector uses "accessibility" to collect/free the pointers).

Usual method to break the cycle is to use std::weak_ptr .

You created a circular dependency: Each Foo contains shared_ptr s (ie shares ownership) of all other Foo s. This means no Foo will ever get destructed: To be destroyed, the use_count would have to be zero. But it cannot be zero before entering the destructor because every other Foo still holds a reference.

This is a classic case of the limits of shared ownership - contrary to some beliefs, it does not automagically solve all your ownership problems.

I would also question the point of each Foo storing the same pointer to all Foo s. If that's what you want to do, it should just be static , but that doesn't sound like good design either. Maybe you could detail the actual problem you want so solve (in a new question)?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM