共享指针

共享指针(Shared Pointers) 是指既健壮、又能为空指针的智能指针。共享指针沿袭了普通智能指针的所有优点,它能避免出现内存泄漏、悬挂指针,还能避免指针指向未初始化的内存。但它们还有一些其他特点,例如:

  • 共享所有权(Shared Ownership): 引用计数支持多个共享指针,以确保它们引用的数据对象永远不被删除,前提是它们中的任意一个仍指向数据对象。
  • 自动失效(Automatic Invalidation): 你可安全引用易变对象,无需担心出现悬挂指针。
  • 弱引用: 弱指针可中断引用循环。
  • 意向指示(Indication of Intent): 区分拥有者(参见共享引用)和观察者,并提供不可为空的引用。

共享指针有一些值得注意的基本特性,包括:

  • 语法非常健壮
  • 非侵入式(但能反射)
  • 线程安全(视情况而定)
  • 性能佳,占用内存少

共享指针类似于共享引用,主要区别在于共享引用不可为空,因此会始终引用有效对象。在共享引用和共享指针之间进行选择时,除非需要空对象或可为空的对象,否则建议你优先选择共享引用。

声明和初始化

因为共享指针可为空,所以无论有无数据对象,都可以对它们进行初始化。以下是创建共享指针的一些示例:

    // 创建一个空白的共享指针
    TSharedPtr EmptyPointer;
    // 为新对象创建一个共享指针
    TSharedPtr NewPointer(new FMyObjectType());
    // 从共享引用创建一个共享指针
    TSharedRef NewReference(new FMyObjectType());
    TSharedPtr PointerFromReference = NewReference;
    // 创建一个线程安全的共享指针
    TSharedPtr NewThreadsafePointer = MakeShared(MyArgs);

Copy full snippet

// 创建一个空白的共享指针 TSharedPtr<FMyObjectType> EmptyPointer; // 为新对象创建一个共享指针 TSharedPtr<FMyObjectType> NewPointer(new FMyObjectType()); // 从共享引用创建一个共享指针 TSharedRef<FMyObjectType> NewReference(new FMyObjectType()); TSharedPtr<FMyObjectType> PointerFromReference = NewReference; // 创建一个线程安全的共享指针 TSharedPtr<FMyObjectType, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FMyObjectType, ESPMode::ThreadSafe>(MyArgs);

在第二个示例中,NodePtr 实际上拥有新的 FMyObjectType 对象,因为没有其他共享指针引用该对象。如果 NodePtr 超出范围,并且没有其他共享指针或共享引用指向该对象,那么该对象将被销毁。

复制共享指针时,系统将向它引用的对象添加一个引用。

    // 增加任意对象ExistingSharedPointer引用的引用数。
    TSharedPtr AnotherPointer = ExistingSharedPointer;

Copy full snippet

// 增加任意对象ExistingSharedPointer引用的引用数。 TSharedPtr<FMyObjectType> AnotherPointer = ExistingSharedPointer;

对象将持续存在,直到不再有共享指针(或共享引用)引用它为止。

你可以使用 Reset 函数、或分配一个空指针来重设共享指针,如下所示:

    PointerOne.Reset();
    PointerTwo = nullptr;
    // PointerOne和PointerTwo现在都引用nullptr。

Copy full snippet

PointerOne.Reset(); PointerTwo = nullptr; // PointerOne和PointerTwo现在都引用nullptr。

你可以使用 MoveTemp(或 MoveTempIfPossible)函数将一个共享指针的内容转移到另一个共享指针,将原始的共享指针保留为空:

    // 将PointerOne的内容移至PointerTwo。在此之后,PointerOne将引用nullptr。
    PointerTwo = MoveTemp(PointerOne);
    // 将PointerTwo的内容移至PointerOne。在此之后,PointerTwo将引用nullptr。
    PointerOne = MoveTempIfPossible(PointerTwo);

Copy full snippet

// 将PointerOne的内容移至PointerTwo。在此之后,PointerOne将引用nullptr。 PointerTwo = MoveTemp(PointerOne); // 将PointerTwo的内容移至PointerOne。在此之后,PointerTwo将引用nullptr。 PointerOne = MoveTempIfPossible(PointerTwo);

MoveTemp 和 MoveTempIfPossible 的唯一不同之处在于 MoveTemp 包含静态断言,强制其只能在非常量左值(lvalue)上执行。

在共享指针与共享引用之间进行转换

在共享指针与共享引用之间进行转换是一种常见的做法。共享引用隐式地转换为共享指针,并提供新的共享指针将引用有效对象的额外保证。转换由普通语法处理:

    TSharedPtr MySharedPointer = MySharedReference;

Copy full snippet

TSharedPtr<FMyObjectType> MySharedPointer = MySharedReference;

只要共享指针引用了一个非空对象,你就可以使用 Shared Pointer 函数 ToSharedRef 从此共享指针创建一个共享引用。试图从空共享指针创建共享引用将导致程序断言。

    // 在解引用之前,请确保共享指针有效,以避免可能出现的断言。
    if (MySharedPointer.IsValid())
    {
        MySharedReference = MySharedPointer.ToSharedRef();
    }

Copy full snippet

// 在解引用之前,请确保共享指针有效,以避免可能出现的断言。 if (MySharedPointer.IsValid()) { MySharedReference = MySharedPointer.ToSharedRef(); }

对比

你可以测试共享指针彼此间的相等性。在此情境中,相等被定义为两个共享指针引用同一对象。

    TSharedPtr NodeA, NodeB;
    if (NodeA == NodeB)
    {
        // ...
    }

Copy full snippet

TSharedPtr<FTreeNode> NodeA, NodeB; if (NodeA == NodeB) { // ... }

IsValid 函数和 bool 运算符有助于判断共享指针是否引用了有效对象。你还可以调用 Get,查看它是否返回有效的(非空)对象指针。

    if (Node.IsValid())
    {
        // ...
    }
    if (Node)
    {
        // ...
    }
    if (Node.Get() != nullptr)
    {
        // ...
    }

Copy full snippet

if (Node.IsValid()) { // ... } if (Node) { // ... } if (Node.Get() != nullptr) { // ... }

解引用和访问

你可以像使用普通C++指针那样解引用,调用方法和访问成员。你也可以像使用其他C++指针那样,通过调用 IsValid 函数或使用重载的 bool 运算符,在取消引用之前执行空检查。

    // 在解引用前,检查节点是否引用了一个有效对象。
    if (Node)
    {
        // 以下三行代码中的任意一行都能解引用节点,并且对它的对象调用ListChildren:
        Node->ListChildren();
        Node.Get()->ListChildren();
        (*Node).ListChildren();
    }

Copy full snippet

// 在解引用前,检查节点是否引用了一个有效对象。 if (Node) { // 以下三行代码中的任意一行都能解引用节点,并且对它的对象调用ListChildren: Node->ListChildren(); Node.Get()->ListChildren(); (*Node).ListChildren(); }

自定义删除器

共享指针和共享引用支持对它们引用的对象使用自定义删除器。如需运行自定义删除代码,请提供lambda函数,作为创建智能指针时使用的参数,就像这样:

    void DestroyMyObjectType(FMyObjectType* ObjectAboutToBeDeleted)
    {
        // 此处添加删除代码。
    }
    // 这些函数使用自定义删除器创建指南指针。
    TSharedRef NewReference(new FMyObjectType(), [](FMyObjectType* Obj){ DestroyMyObjectType(Obj); });

    TSharedPtr NewPointer(new FMyObjectType(), [](FMyObjectType* Obj){ DestroyMyObjectType(Obj); });
Copy full snippet

void DestroyMyObjectType(FMyObjectType* ObjectAboutToBeDeleted) { // 此处添加删除代码。 } // 这些函数使用自定义删除器创建指南指针。 TSharedRef<FMyObjectType> NewReference(new FMyObjectType(), { DestroyMyObjectType(Obj); });

TSharedPtr&lt;FMyObjectType> NewPointer(new FMyObjectType(), [](FMyObjectType* Obj){ DestroyMyObjectType(Obj); });