4

假设我有一个管理器类,其中包含某个对象的向量:

class SomeObjectManager
{
private:
    std::vector<SomeObject> _heldObjects;
};

在那个类中,我有一些函数可以遍历所述向量以返回请求的对象。

SomeObject getSomeObjectByName(std::string nameToFind);

我需要知道的是什么时候使用智能指针合适。我真的应该返回类似下面的东西吗?

std::shared_ptr<SomeObject> getSomeObjectByName(std::string nameToFind);

或者我应该使用其他类似 unique_ptr 或 weak_ptr 的东西吗?我希望 SomeObjectManager 类拥有返回的实际对象,并且永远不会说 SomeObject 被删除,除非 Manager 这样做。

我在 C# 模式下呆了很长一段时间后才回到 C++ 世界。感谢您的帮助并消除了我的困惑。

我已经阅读了很多关于此事的信息,但从未真正找到针对我的特殊情况的直接答案。


编辑#1

我想用这个来改写我最后几句话:

我希望 SomeObjectManager 类拥有正在返回的实际对象,并且从未说过 SomeObject 从向量中删除并随后被删除,超出范围,直到 Manager 强制它这样做。例如:

void SomeObjectManager::removeSomeObjectByName(const std::string& objectToRemove);

这只会遍历向量,找到 said SomeObject,然后将其从向量中删除。

4

6 回答 6

5

由于SomeObjectManager是实例的所有者(存储在其数据成员中),我只返回原始指针,因为它们实际上是在观察指针。SomeObjectstd::vector

std::vector<SomeObject> _heldObjects;

SomeObject* getSomeObjectByName(const std::string& nameToFind) {
    ... find index of object corresponding to 'nameToFind'
    return &_heldObjects[foundIndex];
}

(请注意,我nameToFind使用对 的引用传递const,因为我假设这nameToFind是一个输入字符串,因此如果在方法内部您只是观察该字符串,则可以避免使用 进行深度复制const &)。

当你拥有原始指针时必须注意(它们应该被包裹在安全的 RAII 边界内),但是观察原始指针是可以的。

只需确保 的生命周期SomeObjectManager超过其客户端的生命周期,以确保客户端引用有效对象。

另请注意,如果您将新项目添加到向量数据成员(例如使用std::vector::push_back()),则存储在向量中的先前实例的地址可能会更改。SomeObject所以,如果你把指针指向外面的那些,它们就会变得无效。

因此,在将指向其元素的指针指向外部客户端代码之前,请确保未更改矢量大小和矢量内容。

另一种方法是std::vector<std::unique_ptr<SomeObject>>作为数据成员。在这种情况下,即使调整了向量的大小,使用智能指针(尤其是 using std::unique_ptr::get())返回的地址仍然有效:

std::vector<std::unique_ptr<SomeObject>> _heldObjects;

SomeObject* getSomeObjectByName(const std::string& nameToFind) {
    ... find index of object corresponding to 'nameToFind'
    return _heldObjects[foundIndex].get();
}

PS
另一个选项可能是返回对的引用const SomeObject(假设这种使用const在您的设计中有意义):

std::vector<SomeObject> _heldObjects;

const SomeObject& getSomeObjectByName(const std::string& nameToFind) const {
    ... find index of object corresponding to 'nameToFind'
    return _heldObjects[foundIndex];
}
于 2014-04-15T15:38:08.250 回答
3

如果您的程序在单线程vector中运行,那么如果您有足够的纪律,您通常可以很好地返回原始指针或对存储在 中的对象的引用。

由于管理器私有地拥有vector和 里面的对象,因此可以控制何时删除对象,您可以确保没有指向已删除对象的无效指针(这不是自动保证的!)。
基本上,管理器只有在知道没有人持有对该对象的引用时才必须删除对象,例如仅在明确定义的不同时间执行此操作(例如在程序结束时,或者当它知道没有消费者存在时,或诸如此类)。
引用计数是这样做的一种方式,它也是shared_ptr内部的(嗯,不......严格按照规范的字母,引用计数不是必需的,只定义了可见的行为,但实际上所有实现都这样做)。

因此,“删除”对象的过程只会减少引用计数器(很像在托管语言中),并且当引用计数器达到零时,对象将真正停止存在。当您“删除”它时,可能但不一定立即发生这种情况。在对象实际被销毁之前可能还需要一些时间。
该方法“自动”工作,无需大量的勤奋和严格的纪律,并且可以简单地通过将shared_ptr对象的 s 存储在向量中并返回shared_ptrs 或weak_ptrs 来实现。

稍等!那为什么即使weak_ptr你也可以返回a,为什么还有sshared_ptr呢?他们在逻辑上和实践上做不同的事情。shared_ptrs 拥有(至少部分),而weak_ptrs 没有。此外,weak_ptr复制 s 更便宜。

多线程程序中,存储shared_ptrs 并返回 aweak_ptr是唯一安全的方法。不拥有资源,因此weak_ptr无法阻止管理器删除对象,但它为持有者提供了一种可靠且明确的方式来了解资源是否有效以及资源在您使用时将保持有效。

你返回那个weak_ptr,当消费者真正想要使用这个对象时,它会将 转换weak_ptr为一个临时的shared_ptr。这将失败(给出一个空指针),因此消费者知道该对象已被删除,并且它可能不会使用它。或者,它会成功,现在消费者有一个有效的指针,该指针具有一个对象的共享所有权,现在保证在使用它时保持有效

在“有效”和“无效”之间没有任何东西,没有猜测,没有任何东西可以失败。如果您成功转换为有效的临时shared_ptr文件,那么您就可以开始了。否则,对象就消失了,但你知道
就安全性而言,这是一个很大的优势。即使管理器在您使用它时“删除”了该对象,您的程序也不会崩溃或产生垃圾,该对象在您停止使用之前一直有效!

可以说,这在某种程度上模糊了“管理器在选择这样做时删除对象”的范式,但它确实是安全执行此操作的唯一方法。管理器仍然是控制要删除哪些对象的人,它只能在对象正在使用时立即删除它(这可能会导致可怕的灾难)。但是,它可以随时通过删除它shared_ptr并因此减少引用计数来安排下一次可能的删除。

唯一明显的阻碍是必须立即销毁对象的情况(因为析构函数具有必须立即立即发生的副作用)。但在这种情况下,要获得并发访问权是非常困难的(一场噩梦!)。幸运的是,这也是一种非常罕见的情况。

于 2014-04-15T16:57:42.467 回答
2

从您的函数返回对 SomeObject 的引用(或常规指针)。只要引用保留在向量中,并且向量未重新分配(注意这一点,可能使用列表代替或 unique_ptr 的向量),该引用就有效。当从向量中移除时,该对象已失效,并且对它的所有引用都不再有效。(再次小心删除中间的元素)

于 2014-04-15T15:28:41.763 回答
2

如果您没有将对象存储为 std::shared_ptr,那么返回 std::shared_ptr 就没有任何意义。甚至不知道你将如何去做。我认为没有办法将已经存在的指针包装在智能指针中。如果那里已经有数据,则可以返回一个常规的 const 指针。这样,您将避免复制对象内容所需的开销。

于 2014-04-15T16:24:40.130 回答
0

在这种情况下,您可以选择使用shared_ptr或。weak_ptr您使用哪个取决于您想要的对象的生命周期。

如果您只希望对象在SomeObjectManager引用它并且客户端当时正在使用它时有效,则使用weak_ptr. 如果您希望引用保持有效(如果SomeObjectManager有引用并且客户端存储了对它的引用)。

这是一个带有weak_ptr.

std::weak_ptr<SomeObject> weakref = getSomeObject();   
// weakref will not keep the object alive if it is removed from the object manager.

auto strongref = weakref.lock();
if ( strongref ) {
     // strongref is a shared_ptr and will keep the object alive until it 
     // goes out of scope.
}

这在多线程环境中很有用,因为shared_ptr引用计数访问是线程安全的。但是,这确实意味着客户端可以将对象的生命周期延长到比您想要的更长的时间。

于 2014-04-15T15:38:01.003 回答
-1

如果要使用智能共享指针,向量本身应该使用智能指针。

class SomeObjectManager
{
private:
    std::vector<std::shared_ptr<SomeObject> > _heldObjects;
};

但这样你就安全了。

于 2014-04-15T15:34:47.767 回答