对象std::shared_ptr是否可以通过其std::weak_ptr找到?

4
所以我有一个std::vector<std::shared_ptr<T>> myListOfT;,并且我有一个std::weak_ptr<T> ptrToOneT;,它是从用于填充该容器的指针之一创建的(假设我将其放在回调函数中)。在该容器和我的weak_ptr上使用std::find,是否会给我一个迭代器来指向原始的shared_ptr(如果这样的指针存在于集合中)?这是否在标准中得到保证,还是这取决于实现?

简短的回答是可以,但在进行比较之前,您可能需要将get()返回的值转换为绝对基指针。 - Captain Obvlious
4
使用 weak_ptr::lock() 获得一个 shared_ptr,然后将其传递给 std::find。您将找到指向相同对象的某些指针。 - Igor Tandetnik
此外,通过锁定,您可以防止弱引用指向的对象在引用计数归零(例如在另一个线程中)时被删除。 - Alexandre C.
就像@IgorTandetnik所说的那样。当你有一个weak_ptr时,你可能可以使用.lock()从中获取一个shared_ptr,也可能不行。没有必要搜索以找到指针。如果你想要迭代器,请锁定weak_ptr并搜索结果的shared_ptr - Kent
@IgorTandetnik 在评论中回答! - Barry
3个回答

4
我们可以使用 std::weak_ptr::owner_before 来避免锁定 weak_ptr。我会使用一个比必要更冗长的解决方案,并引入 owner_equal,它是 std::owner_less 的对应物:
template<typename T>
class owner_equal
{
private:
    template<typename L, typename R>
    static bool e(L const& l, R const& r)
    { return !(l.owner_before(r)) && !(r.owner_before(l)); }

public:
    using S = std::shared_ptr<T>;
    using W = std::weak_ptr<T>;

    bool operator()(S const& l, W const& r) const { return e(l, r); }
    bool operator()(W const& l, S const& r) const { return e(l, r); }
};

使用这个函数对象类型,我们可以自定义std::find_if
using T = int;
std::vector<std::shared_ptr<T>> myListOfT =
  {std::make_shared<int>(0), std::make_shared<int>(1), std::make_shared<int>(2)};

int const pos = 1;
std::weak_ptr<T> ptrToOneT = myListOfT[pos];

auto pred = [&ptrToOneT](std::shared_ptr<T> const& e)
            { return owner_equal<T>{}(e, ptrToOneT); };

auto const r = std::find_if(begin(myListOfT), end(myListOfT), pred);
assert(r - begin(myListOfT) == pos);

Lambda 可以被绑定表达式替换,例如:
auto pred = std::bind(owner_equal<T>{}, std::cref(ptrToOneT),
                      std::placeholders::_1);

的英译中为:“
”。

@davidhigh提出了一个优化建议:

template<typename FwdIt, typename T>
FwdIt findWeakPtr(FwdIt b, FwdIt e, std::weak_ptr<T> const& w)
{
    if(w.expired()) return e;
    else
    {
        auto pred = [&w](std::shared_ptr<T> const& e)
                    { return owner_equal<T>{}(e, w); };
        return std::find_if(b, e, pred);
    }
}

(未经测试)
此外,这也略微改变了行为:如果weak_ptr是“空的”,例如它是从一个空的shared_ptr或通过默认构造函数创建的,则它将通过owner_equal与任何空的shared_ptr相等。但是,在这种情况下,weak_ptr::expired返回true。因此,优化版本将无法在范围内找到空的共享指针。
应该在范围内找到空的共享指针吗?
考虑:
using T = int;
std::vector<std::shared_ptr<T>> myListOfT =
  {std::shared_ptr<T>(), std::shared_ptr<T>()};

int const pos = 1;
std::weak_ptr<T> ptrToOneT = myListOfT[pos];

auto const r = my_weak_ptr_find(begin(myListOfT), end(myListOfT), ptrToOneT);
auto const r_pos = r - begin(myListOfT);

空的共享指针是相等的。因此,如果允许查找空的共享指针,则可能出现r_pos!= pos && r!= end(myListOfT)的情况。例如,本答案中算法的第一个版本会产生r_pos == 0
额外的背景信息,请参见:

1
+1,很棒的发现,但我认为如果weak_ptr已过期,您可以绕过整个搜索过程吗? 这样做可以达到O(1)而不是O(N)。 - davidhigh
2
@davidhigh 有趣。但是可能值得测量一下小范围,因为我猜expired需要原子读取。 - dyp
1
据我理解,我的评论仅适用于您是否想从搜索中删除nullptr == nullptr的匹配项(?)-这至少是我直觉上想要的...其他人可能不想,那么这显然是有利的。 - davidhigh
@davidhigh 嗯,说得对。出于某种原因,我认为不能有空的weak_ptr(只有过期的)。 - dyp

1
我几天前在自己的代码中遇到了类似的问题。根据我的SO研究,可以实现您所要求的功能。锁定弱指针,如果共享指针未过期,则使用std::find
struct A{};

int main()
{
    std::vector<std::shared_ptr<A> > sptr_vec;
    std::weak_ptr<A> wptr;

    if(auto sptr = wptr.lock())
    {
        auto it = std::find(std::begin(sptr_vec), std::end(sptr_vec), sptr);
        if (it != std::end(sptr_vec))
        {
            std::cout<<"found"<<std::endl;
        }
    }
}

注意,C++标准本身在这里并不那么相关--共享指针的比较核心是对包含的原始指针进行比较,即比较内存中的地址。
或者,如果您有一个weak指针向量,您可以使用带有实时锁定谓词的std::find_if。
    std::vector<std::weak_ptr<A> > wptr_vec;
    std::shared_ptr<A> sptr;

    auto it = std::find_if(std::begin(wptr_vec), std::end(wptr_vec)
                         , [&sptr](auto const& w){ auto s = w.lock();
                                                   if (s) {return s == sptr;}
                                                   return false; });
    if (it != std::end(wptr_vec))
    {
        std::cout<<"found"<<std::endl;
    }

请注意,对于此应用程序,我认为将nullptr与自身等同起来是不必要的,即nullptr == nullptrtrue。因此,我从谓词(以及第一个代码块中的搜索)中排除了这种情况。

编辑:刚才考虑了@dyp提出的owner_lock解决方案,如果只涉及搜索,则具有优势。


1

std::weak_ptr::lock()是将weak_ptr“提升”为shared_ptr的方法:

std::weak_ptr<T> ptrToOneT;
auto observe = ptrToOneT.lock();
if (observe) {
    // observe now shares ownership of the one T
}
else {
    // there is no managed object or it has already
    // been destroyed
}

如果lock()成功,那么你就拥有了一个正常的std::shared_ptr<T>,你可以像使用容器中的任何其他对象一样使用它进行find()。虽然你可能不需要find()它,因为你已经拥有它(除非你想要erase()它或者做些其他的事情)。
顺便提一下,对于shared_ptr,引用“原始的shared_ptr”并没有真正意义。

1
std::owner_less 不能使用吗? - dyp
@dyp 我不确定。我以前从来没有听说过这个。 - Barry
@dyp 的回答听起来比这个更好。 - Barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接