std::move return weak_ptr::lock 的值弄乱了 shared_ptr 的引用计数?
std::move return value of weak_ptr::lock messing up reference count of shared_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
这是一个class自己持有一个weak_ptr,所以它可以给自己shared_ptr-handles。它是真实代码中的资源-class,shared_ptr 处理那些被传递的资源。现在为了减少复制 shared_ptrs,我遇到了我的 getHandle 函数(上面的 getR/getL)并希望它通过移动而不是复制来 return。在简短的测试中,std::moving weak_ptr::lock 的 return 似乎没问题,但在最终代码中它把事情搞砸了。
与复制 return 值相比,它似乎移动它减少了 shared_ptr 的引用计数器 - 所以我最终得到 2 shared_ptrs 存在但都有 use_count() 的 1. 因此,如果我使用 get() 得到的那个超出范围,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
函数,它是函数的局部变量,然后 return 是对它的引用。那是对不再存在的对象的悬挂引用。只是 return 按值 getL
做的(我不知道你为什么称它为 getL
...如果它指的是左值它是错误的,它 return s 是一个右值)。
您误用了 std::move
来提高性能,但简单地 return 对象更简单、更安全,并且允许编译器更有效地优化它。没有 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
成员和你的 backref,然后做:
struct A : std::enable_shared_from_this<A> {
std::string s = "foo";
};
然后调用 keeper->shared_from_this()
而不是 keeper->getL()
。您会注意到 shared_from_this()
returns 按值而不是按引用,以避免 getR()
函数中的错误。
我需要对以下行为的解释:
#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
这是一个class自己持有一个weak_ptr,所以它可以给自己shared_ptr-handles。它是真实代码中的资源-class,shared_ptr 处理那些被传递的资源。现在为了减少复制 shared_ptrs,我遇到了我的 getHandle 函数(上面的 getR/getL)并希望它通过移动而不是复制来 return。在简短的测试中,std::moving weak_ptr::lock 的 return 似乎没问题,但在最终代码中它把事情搞砸了。 与复制 return 值相比,它似乎移动它减少了 shared_ptr 的引用计数器 - 所以我最终得到 2 shared_ptrs 存在但都有 use_count() 的 1. 因此,如果我使用 get() 得到的那个超出范围,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
函数,它是函数的局部变量,然后 return 是对它的引用。那是对不再存在的对象的悬挂引用。只是 return 按值 getL
做的(我不知道你为什么称它为 getL
...如果它指的是左值它是错误的,它 return s 是一个右值)。
您误用了 std::move
来提高性能,但简单地 return 对象更简单、更安全,并且允许编译器更有效地优化它。没有 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
成员和你的 backref,然后做:
struct A : std::enable_shared_from_this<A> {
std::string s = "foo";
};
然后调用 keeper->shared_from_this()
而不是 keeper->getL()
。您会注意到 shared_from_this()
returns 按值而不是按引用,以避免 getR()
函数中的错误。