简体   繁体   中英

std::move return value of weak_ptr::lock messing up reference count of shared_ptr?

i need an explanation of the following behaviour:

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

struct A {
    std::string s = "foo";
    std::weak_ptr<A> h;
    std::shared_ptr<A> && getR() {
        return std::move(h.lock());
    }
    std::shared_ptr<A> getL() {
        return h.lock();
    }
};

std::vector< std::shared_ptr<A> > storage;
std::vector< std::weak_ptr<A> > accountant;

void store(std::shared_ptr<A> && rr) {
    std::cout << "store '" << rr->s << "' uses: " << rr.use_count() << std::endl;
    storage.push_back(std::move(rr));
}

int main() {
    // create keeper of A
    auto keeper = std::make_shared<A>();
    keeper->s = "bar";
    // store weak_ptr-type handle with accountant
    accountant.push_back(keeper);
    // backref handle to A
    keeper->h = accountant[0];

    std::cout << "# case 0: manual 'move'" << std::endl;
    {
        store(std::move(accountant[0].lock()));

        std::cout << "uses: " << keeper.use_count() << std::endl;
    }
    storage.clear();

    std::cout << "# case 1: manual 'move' from internal" << std::endl;
    {
        store(std::move(keeper->h.lock()));

        std::cout << "uses: " << keeper.use_count() << std::endl;
    }
    storage.clear();

    std::cout << "# case 2: return copy from func" << std::endl;
    {
        store(keeper->getL());

        std::cout << "uses: " << keeper.use_count() << std::endl;
    }
    storage.clear();
    // all is well up to here.

    std::cout << "# case 3: return rref from func" << std::endl;
    {
        store(keeper->getR());

        std::cout << "uses: " << keeper.use_count() << std::endl;
        std::cout << "storage[0]: " << storage[0].get() << " uses: " << storage[0].use_count() << " " << &storage[0] << std::endl;
        std::cout << "keeper: " << keeper.get() << " uses: " << keeper.use_count() << " " << &keeper << std::endl;
    }
    storage.clear();

    std::cout << "# after" << std::endl;
    std::cout << "uses: " << keeper.use_count() << std::endl;
    // all the A is gone!!!!
    return 0;
}

output:

# case 0: manual 'move'
store 'bar' uses: 2
uses: 2
# case 1: manual 'move' from internal
store 'bar' uses: 2
uses: 2
# case 2: return copy from func
store 'bar' uses: 2
uses: 2
# case 3: return rref from func
store 'bar' uses: 1
uses: 1
storage[0]: 0x2b49f7a0fc30 uses: 1 0x2b49f7a10ca0
keeper: 0x2b49f7a0fc30 uses: 1 0x7ffd3683be20
# after
uses: 0

ideone: http://ideone.com/smt7TX

This is a class holding a weak_ptr to itself, so it can give out shared_ptr-handles to itself. Its a resource-class in the real code, and shared_ptr handles to those get passed around. Now in an effort to reduce copying shared_ptrs i came across my getHandle function (getR/getL in the above) and wanted it to return by moving instead of copying. In a short test std::moving the return of weak_ptr::lock seemed ok, but in the final code it messed things up bad. In comparison to copying the return-value it seems moving it reduces the shared_ptr's reference counter - so i end up with 2 shared_ptrs in existence but both having a use_count() of 1. so if the one i got using get() goes out of scope the A gets destroyed and my original shared_ptr which is still around points to garbage. In the example code you can see that after case 3 - i would have expected the last cout to tell me a use_count() of 1 until keeper is destroyed.

Now in the real code i just inlined the equivalent of getL in the hopes that this will prevent the superflous copying, but i can't get over not having a clue why this doesn't work as i thought it would.

Why does case 3 reduce the reference count? And then why don't case 0 and 1 also reduce it?

You have a bug here:

std::shared_ptr<A> && getR() {
    return std::move(h.lock());
}

This creates a temporary shared_ptr which is local to the function then returns a reference to it. That is a dangling reference to an object that no longer exists. Just return by value as getL does (I don't know why you've called it getL ... if that refers to an lvalue it's wrong, it returns an rvalue).

You are misusing std::move in a misguided attempt to improve performance, but simply returning the object is simpler, safer, and allows the compiler to optimise it far more effectively. Without the std::move there won't be any copy or move, the compiler will elide the temporary completely, see What are copy elision and return value optimization?

These other moves are also redundant (although not actually harmful here):

    store(std::move(accountant[0].lock()));

    store(std::move(keeper->h.lock()));

In both cases you're trying to move something that is already an rvalue, which is pointless.

Also you have reimplemented std::enable_shared_from_this , poorly. Get rid of your weak_ptr member and your backref and just do:

struct A : std::enable_shared_from_this<A> {
    std::string s = "foo";
};

And then call keeper->shared_from_this() instead of keeper->getL() . You'll notice that shared_from_this() returns by value, not by reference, to avoid the bug in your getR() function.

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