C++ 如果我碰巧注意到弱ptr,我是否应该调用reset';过期了吗?

C++ 如果我碰巧注意到弱ptr,我是否应该调用reset';过期了吗?,c++,shared-ptr,weak-ptr,C++,Shared Ptr,Weak Ptr,我有一组生物对象,这些对象是使用std::make_shared和std::shared_ptr在我的应用程序的一部分中创建和拥有的 我还使用std::weak_ptr跟踪World对象中的零个或一个生物的选择 当我发现不再需要它时,这会释放内存。令人烦恼的是,这个版本的GetSelection不能是const 我的问题是: 在这种情况下,哪个版本的GetSelection被视为最佳实践 如果模板化代码中出现类似的情况,答案会改变吗?sizeof(T)是未知的,并且可能是巨大的?或者在C++14

我有一组
生物
对象,这些对象是使用
std::make_shared
std::shared_ptr
在我的应用程序的一部分中创建和拥有的

我还使用
std::weak_ptr
跟踪
World
对象中的零个或一个
生物的选择

当我发现不再需要它时,这会释放内存。令人烦恼的是,这个版本的
GetSelection
不能是
const

我的问题是:
  • 在这种情况下,哪个版本的
    GetSelection
    被视为最佳实践

  • 如果模板化代码中出现类似的情况,答案会改变吗?
    sizeof(T)
    是未知的,并且可能是巨大的?或者在C++14中,可能涉及到
    std::make_shared

  • 如果第二个版本总是最好的,那么
    std::weak_ptr::expired
    lock
    自己不这样做的理由是什么


  • 首先应该注意的是,
    std::make_shared
    的安置策略是可选的,即标准不要求实现执行此优化。这是一个不具约束力的要求,这意味着完全一致的实现可能会选择放弃它

    回答您的问题:

  • 考虑到您似乎只有一个选择(并且您没有因此而通过保留许多控制块来增加内存使用),我认为应该保持简单。内存是瓶颈吗?这对我来说是一个巨大的挑战。您应该编写更简单的代码,可以在其中应用
    const
    ,然后在需要时返回并进行优化

  • 答案并不是无条件地改变,而是取决于问题领域和瓶颈是什么。如果您正在分配一个“巨大”的对象(比如说100千字节),并且该对象的空间在一个相对未使用的控制块中游荡,直到被替换为止,这可能不是您的瓶颈,也可能不值得编写更多的代码(本质上更容易出错、维护和破译)来“解决”

  • 由于
    std::weak_ptr::lock
    std::weak_ptr::expired
    const
    ,根据C++11的
    const
    解释,它们必须是线程安全的。因此,给定一些
    std::weak_ptr
    ,同时调用
    lock()
    expired()
    的任意组合必须是安全的。在引擎盖下,
    std::weak_ptr
    存储一个指向控制块的指针,它通过该指针检查/增加/等原子计数器,以确定对象是否已过期,或查看是否可以获取锁。如果要在
    std::weak_ptr
    内部实现优化,则必须以某种方式检查控制块的状态,然后在指针过期时自动删除指向控制块的指针。这将导致每次访问
    std::weak_ptr
    ,都会产生开销(即使这可以通过原子实现,也会有开销),所有这些都是为了一个小的优化

  • 对于绝大多数情况,
    GetSelection
    的第一个版本更好。此版本可以是
    const
    ,不需要额外的同步代码即可实现线程安全

  • 在无法提前预测确切使用模式的通用库代码中,仍然首选第一个版本。但是,在同步代码已经到位的情况下,保护对
    弱\u ptr
    的访问,插入调用
    reset
    释放内存并在以后更快地使用指针不会有什么坏处。这个非常小的优化本身不值得放入同步代码

  • 鉴于前两个答案,最后一个问题没有实际意义。但是,有两个强有力的理由可以说明,当指针过期时,没有
    弱\ptr::lock
    自动重置指针:

    • 有了这种行为,就不可能在
  • 之前实现
    弱ptr::owner\u,从而使用
    弱ptr
    作为关联容器中的键类型

  • 此外,如果没有额外的同步代码,即使在活动对象上使用
    弱\u ptr::lock
    ,也无法实现。这将导致性能损失,远远大于更急切地释放内存所带来的微小收益

  • 替代解决方案:

    如果浪费的内存被认为是一个需要解决的实际问题(可能共享对象确实很大,并且/或者目标平台的内存非常有限),另一个选择是使用
    shared\u ptr(new T)
    创建共享对象,而不是
    make\u shared
    。这将更早地释放分配给T的内存(当指向它的最后一个
    共享\u ptr
    被破坏时),而小控制块将单独存在。

    我将发布我自己的(不完全令人满意)过一会儿再回答,但我真的很想看看其他人怎么说。如果你想
    GetSelection
    保持
    const
    ,你可以在第二种情况下使
    弱ptr
    成员
    可变。您的意思是,在
    selection.lock()
    返回一个空的
    std::shared_ptr
    之后,以前管理的对象不知何故没有释放?@T.C.正确,但我会说“令人恼火的是,此版本要求我的选择成员是可变的”。关键是我根本没有改变
    弱ptr
    对象的可观察状态,我只是在尝试一点优化,可以说这可以在
    弱ptr
    内部完成。这直接关系到
    void World::SetSelection(const std::shared_ptr<Creature>& creature) {
        selection = creature;
    }
    
    std::shared_ptr<Creature> World::GetSelection() const {
        return selection.lock();
    }
    
    std::shared_ptr<Creature> World::GetSelection() {
        const auto ret = selection.lock();
        if (!ret)
            selection.reset();
    
        return ret;
    }