c++ - std::move weak_ptr::lock 的返回值弄乱了 shared_ptr 的引用计数?

标签 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_ptr,我遇到了我的 getHandle 函数(上面的 getR/getL),并希望它通过移动而不是复制来返回。在一个简短的测试中,std::moving weak_ptr::lock 的返回似乎没问题,但在最终代码中它把事情搞砸了。 与复制返回值相比,它似乎在移动它减少了 shared_ptr 的引用计数器 - 所以我最终存在 2 个 shared_ptr,但它们的 use_count() 都是 1。所以如果我使用 get() 的那个消失了scope A 被破坏,我原来的 shared_ptr 仍然指向垃圾。 在示例代码中,您可以看到在案例 3 之后 - 我本以为最后一个 cout 会告诉我 use_count() 为 1,直到 keeper 被销毁。

现在在实际代码中,我只是内联了 getL 的等价物,希望这能防止过度复制,但我无法克服不知道为什么这不像我想的那样工作的线索。

为什么案例3会减少引用计数? 那么为什么 case 0 和 1 不减少它呢?

最佳答案

这里有一个错误:

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?

这些其他的 Action 也是多余的(虽然在这里实际上并没有害处):

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

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

在这两种情况下,您都试图移动已经是右值的东西,这是毫无意义的。

您还重新实现了 std::enable_shared_from_this , 不好。摆脱你的 weak_ptr 成员和你的 backref,然后做:

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

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

关于c++ - std::move weak_ptr::lock 的返回值弄乱了 shared_ptr 的引用计数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43784060/

相关文章:

c++ - 使用 SFINAE 的非模板类中的模板函数重载

c++ - 模板函数中的 std::function

c++ - 如何将可变参数模板参数的特征值减少为一个值?

c++ - 使用 C++ 在 ffmpeg 中解码为特定像素格式

c++ - 重新解释没有类型标识符的强制转换 void

c++ - Global const string& 对我来说很难闻,它真的安全吗?

c++ - 仅在具有零参数模板的头文件中定义 C++ 全局函数?

c++ - 输出不打印?

c++ - 输入特征以检查类是否具有成员函数

c++ - 类模板如何存储引用或值?