c++ 11智能指针所有权和类型转换

C++ 11 Smart Pointer Ownership and Casting

本文关键字:类型转换 所有权 指针 智能 c++      更新时间:2023-10-16

我有一个基本实体类和派生类,如奶牛和鸡…

using namespace std;
class Entity
{
    list<shared_ptr<Relationship>> relationships;
    void createRelationship(weak_ptr<Entity> other,.... other stuff)
    //...
    virtual ~Entity()
}
class Cow: public Entity
{
    //...
}
class Chicken : public Entity
....etc...

我正在努力学习用std智能指针正确管理内存。我现在做事情的方式,唯一的地方我的派生类曾经住在共享指针的向量…

vector<shared_ptr<Cow>>
vector<shared_ptr<Chicken>>
etc.

我的Entity类负责管理任何两个实体之间的关系,无论它们是否具有相同的类型。为此,它保留了一个Relationship对象列表,看起来像…

class Relationship
{
  weak_ptr<Entity> from;
  weak_ptr<Entity> to;
etc....
}

我使用弱指针是因为牛或鸡可能会死亡,在这种情况下,它们与其他实体的关系将失效。

这就是我的问题。我将所有内容存储为指向派生类的共享指针,但实体类中的所有代码都使用指向基类的弱指针。我经常需要将弱实体指针转换为共享母牛指针,或者将共享母牛指针转换为弱实体指针。

不知何故,我的代码允许我在上面Entity类的createrrelationship(…)的参数中传递shared_ptr对象。我真的不知道为什么会编译,我想知道这样做是否有效。我是否应该手动将其转换为弱指针,然后使用static_pointer_cast进行强制转换?(我问这个问题是因为我读到过将共享指针作为参数传递很慢,我担心会发生这种情况)。

在另一个方向上,有时我知道相同类型的两个实体之间存在某些关系。为了说明我的观点,以需要从它的父母那里继承基因的母牛为例:它通过它的关系来查找亲子关系,然后访问指向它的父母的弱实体指针。为了访问它们的遗传成员变量,它需要将这些指向实体的弱指针转换为指向奶牛的共享指针。我一直在使用weak_ptr.lock()和dynamic_pointer_cast来实现这一点。

这是执行这两个(反向相关)类型转换的有效方法吗?任何一般性的评论或参考材料都是值得赞赏的,因为我正试图有效地使用这些指针。

听起来你主要有三个问题:

  1. 当您必须频繁转换为shared_ptr以使用其值时,存储weak_ptr是否有效?
  2. 为什么可以用shared_ptr<T>构造weak_ptr<T> ?
  3. 使用static_ptr_cast还是dynamic_pointer_cast比较好

问题2是最简单的;正如Vaughn提到的,weak_ptr有一个来自shared_ptr的构造函数。

问题1和3更加模糊。为了解决这个问题,让我们来看看为什么你听说通过shared_ptr很慢。当您按值传递shared_ptr时,它必须复制shared_ptr,并且复制它涉及底层引用计数的原子增量。这种原子增量有很多优点和缺点,但简单地说,如果您不需要跟踪所有权,这是一种不必要的开销。(在大多数情况下担心这一点可能是过早的优化,但c++语言希望确保在必要时您可以担心这类事情。)

复制weak_ptr会比复制shared_ptr快吗?我没有运行任何基准测试,但我猜没有。实际上有两个引用计数,一个用于拥有引用(shared_ptr副本),一个用于非拥有引用(weak_ptr副本)。每一种方法都有相同的原子更新要求,因此不会显著提高速度。我猜想,从理论上讲,weak_ptr的析构函数不必检查结果引用计数并删除对象,因此,如果您所做的只是复制,则减少了一个分支。但这种用法不太可能;有可能你会通过lock()转换回shared_ptr

这就把我们带回了问题1的核心。从观察weak_ptr中获得shared_ptr的开销有多少?大约相当于复制shared_ptr的原子引用计数,加上消费代码上需要的分支,以确保它成功,或者在它失败时处理它。因此,与其考虑效率,不如考虑所有权和对象生命周期。您会遇到lock()将返回null shared_ptr的情况吗?如果没有,您可以使用观察原始指针。如果底层shared_ptr对象可能在weak_ptr对象消失之前消失,则需要对其进行过期检查。如果这是一个瓶颈,看看你是否能找到一种方法来保证生命周期。

最后回到问题3。我在这里回答的时候并没有真正了解这些类型;相反,我是基于shared_ptr是如何工作的。static_pointer_castdynamic_pointer_castconst_pointer_cast都返回指向相同底层对象的shared_ptr实例。因此,它们执行了引用计数的原子增量。因此,它们的开销大致是静态、动态或const强制转换加上shared_ptr复制构造函数的开销。shared_ptr部分对整个程序来说不太可能是重要的,并且在强制转换部分,只有dynamic_pointer_cast中的dynamic_cast可能有可测量的开销(静态和常量强制转换几乎完全是编译时操作)。

所以我们又回到了所有权和对象生命周期。如果你有一个明确的所有权,它可以给你你想要的生命周期,如果你也在写你的性能瓶颈的代码,你会更高兴与unique_ptr和观察原始指针(这很好;"规则"没有拥有原始指针)。但是,如果它不是瓶颈,或者如果对象生命周期不那么容易保证,shared_ptrweak_ptr绝对是方便的,以尽可能小的成本来保证它们的语义。