std :: move返回weak_ptr :: lock的返回值搞乱了shared_ptr的引用计数?

时间:2017-05-04 13:20:00

标签: c++ c++11 c++14 shared-ptr weak-ptr

我需要解释以下行为:

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

输出:

# 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

这是一个将weak_ptr保存到自身的类,因此它可以为自己提供shared_ptr-handles。它是真实代码中的资源类,而shared_ptr是处理那些传递给它们的句柄。现在为了减少复制shared_ptrs,我遇到了我的getHandle函数(上面的getR / getL)并希望它通过移动而不是复制来返回。在一个简短的测试中,std :: move返回weak_ptr :: lock似乎没问题,但是在最终的代码中,它搞糟了。 与复制返回值相比,它似乎移动了它减少了shared_ptr的引用计数器 - 所以我最终得到了2个shared_ptrs,但两者的use_count()都是1.所以如果我使用了get(超出范围,A被摧毁,我原来的shared_ptr仍然指向垃圾。 在示例代码中,您可以看到在案例3之后 - 我会期望最后一个cout告诉我use_count()为1,直到守护者被销毁。

现在在真实的代码中我只是内联了相当于getL的希望,这将阻止超级复制,但我无法克服为什么这不能像我想象的那样工作它会。

为什么案例3会减少引用次数? 那么为什么不是0和1的情况也会减少呢?

1 个答案:

答案 0 :(得分:8)

你有一个错误:

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

这会创建一个临时的shared_ptr,它是函数的本地函数,然后返回对它的引用。这是对不再存在的对象的悬空引用。只需按getL的值返回(我不知道为什么你称之为getL ...如果它引用了左值,那么它会返回一个右值)。

您在误导性尝试中误导std::move以提高性能,但只是返回对象更简单,更安全,并允许编译器更有效地优化它。如果std::move没有任何副本移动,编译器将完全忽略临时,请参阅What are copy elision and return value optimization?

这些其他举动也是多余的(尽管这里实际上并不有害):

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

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

在这两种情况下,你都试图移动已经是左值的东西,这是没有意义的。

你也重新实现了std::enable_shared_from_this。摆脱你的weak_ptr成员和你的背叛,然后做:

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

然后拨打keeper->shared_from_this()而不是keeper->getL()。您会注意到shared_from_this()按值返回,而不是按引用返回,以避免getR()函数中的错误。