10

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

我还Creature使用.Worldstd::weak_ptr<Creature>

void World::SetSelection(const std::shared_ptr<Creature>& creature) {
    selection = creature;
}

std::shared_ptr<Creature> World::GetSelection() const {
    return selection.lock();
}

的调用者GetSelection负责检查指针是否为空。如果是,则表示当前没有选择。

This all works perfectly to my liking: when the selected Creaturedies of natural causes (elsewhere in the application), GetSelectionstarts returning a nullptragain as if nothing was ever selected.

但是在这种情况下,World::selection成员仍然指向std::shared_ptr的控制块。这可能很大,因为我std::make_shared用来创建我的Creature对象(我意识到该Creature对象在正确的时间被正确地销毁,但它的内存仍然被分配)。我正在考虑改成GetSelection这样:

std::shared_ptr<Creature> World::GetSelection() {
    const auto ret = selection.lock();
    if (!ret)
        selection.reset();

    return ret;
}

一旦我注意到不再需要它,这就会释放内存。烦人的是,这个版本的GetSelectioncannot be const

我的问题:

  1. 在这种情况下,哪个版本GetSelection被认为是最佳实践?

  2. 如果在模板代码中发生类似的事情,答案是否会改变,哪里sizeof(T)未知并且可能很大?或者在 C++14 中std::make_shared<T[]>可能涉及到什么?

  3. 如果第二个版本总是最好的,那么自己不做的理由是std::weak_ptr<T>::expired什么lock

4

2 回答 2

4

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

要回答您的问题:

  1. 鉴于您似乎只有一个选择(因此您不会通过保留许多这些控制块来增加内存使用量),我会主张保持简单。内存是瓶颈吗?这对我来说是微优化。您应该编写更简单的代码,您可以在其中应用const,然后在需要时返回并进行优化。

  2. 答案不会无条件地改变,它会根据问题域和瓶颈而改变。如果您正在分配一个“巨大”的对象(例如一百千字节),并且该对象的空间在一个相对未使用的控制块中徘徊,直到被替换,那可能不是您的瓶颈,并且可能不值得编写更多代码(本质上更容易出错、难以维护和破译)来“解决”。

  3. 在C++11的解释下std::weak_ptr::lockstd::weak_ptr::expired它们必须是线程安全的。因此,给定 some ,同时调用和的任意组合必须是安全的。在引擎盖下,存储了一个指向控制块的指针,它通过它来检查/增加/等。原子计数器来确定对象是否已过期,或者查看它是否可以获取锁。如果您想在内部实现优化,您必须以某种方式检查控制块的状态,然后如果指针已过期,则自动删除指向控制块的指针。这会在每次访问constconststd::weak_ptrlock()expired()std::weak_ptrstd::weak_ptrstd::weak_ptr,都是为了一个小的优化。

于 2014-08-12T23:42:54.080 回答
1
  1. 的第一个版本GetSelection更适合绝大多数情况。这个版本可以const并且不需要额外的同步代码来保证线程安全。

  2. 在无法提前预测确切使用模式的通用库代码中,仍然首选第一个版本。然而,在同步代码已经到位以保护weak_ptrreset. 这种非常小的优化本身并不值得放入同步代码。

  3. 鉴于前两个答案,最后一个问题没有实际意义。weak_ptr::lock然而,当发现指针过期时,这里有两个强有力的理由不自动重置指针:

    • 有了这种行为,就不可能实现weak_ptr::owner_before,因此使用 aweak_ptr作为关联容器中的键类型。

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

替代解决方案:
如果浪费的内存被认为是需要解决的实际问题(也许共享对象确实很大和/或目标平台的内存非常有限),另一种选择是创建共享对象shared_ptr<T>(new T)而不是make_shared<T>. 这将更早地释放为 T 分配的内存(当shared_ptr指向它的最后一个被销毁时),而小控制块单独存在。

于 2014-08-13T08:00:07.107 回答