【UE4 C++ 基础知识】<6> 容器——TMap

概述

  • TMap主要由两个类型定义(一个键类型和一个值类型),以关联对的形式存储在映射中。

  • 将数据存储为键值对(TPair<KeyType, ValueType>),只将键用于存储和获取

  • 映射有两种类型:TMap 和 TMultiMap

    • TMap 中的键是唯一的
    • TMultiMap 可存储多个相同的键
  • TMap 也是值类型,支持通常的复制、赋值和析构函数运算,以及它的元素的强所有权。在映射被销毁时,它的元素都会被销毁。键和值也必须为值类型。

  • TMap 是散列容器,这意味着键类型必须支持 GetTypeHash 函数,并提供 运算符== 来比较各个键是否等值

  • TMap 也可使用任选分配器来控制内存分配行为。但不同于 TArray,这些是集合分配器,而不是 FHeapAllocator 和 TInlineAllocator 之类的标准UE4分配器。集合分配器(TSetAllocator类)定义映射应使用的散列桶数量,以及应使用哪个标准UE4分配器来存储散列和元素。

  • KeyFuncs 是最后一个 TMap 模板参数,该参数告知映射如何从元素类型获取键,如何比较两个键是否相等,以及如何对键进行散列计算。这些参数有默认值,它们只会返回对键的引用,使用 运算符== 确定相等性,并调用非成员 GetTypeHash 函数进行散列计算。如果您的键类型支持这些函数,可使用它作为映射键,不需要提供自定义 KeyFuncs。

  • 与 TArray 不同的是,内存中 TMap 元素的相对排序既不可靠也不稳定,对这些元素进行迭代很可能会使它们返回的顺序和它们添加的顺序有所不同。这些元素也不太可能在内存中连续排列。映射的支持数据结构是稀疏数组,这种数组可有效支持元素之间的空位。当元素从映射中被移除时,稀疏数组中就会出现空位。将新的元素添加到数组可填补这些空位。但是,即便 TMap 不会打乱元素来填补空位,指向映射元素的指针仍然可能失效,因为如果存储器被填满,又添加了新的元素,整个存储可能会重新分配。

创建

TMap<int32, FString> FruitMap;  //空TMap,此时尚未分配内存

添加元素

  • Add

    • 元素按插入顺序排列,但不保证这些元素在内存中实际保留此排序
    • 各个键都必定是唯一。如果尝试添加重复键,将替换原来的键值
    • Add 函数可接受不带值的键。调用此重载后的 Add 时,值将被默认构建
    FruitMap.Add(5, TEXT("Banana"));
    FruitMap.Add(2, TEXT("Grapefruit"));
    FruitMap.Add(7, TEXT("Pineapple"));
    // FruitMap == [
    //  { Key:5, Value:"Banana"     },
    //  { Key:2, Value:"Grapefruit" },
    //  { Key:7, Value:"Pineapple"  } ]
    
    FruitMap.Add(2, TEXT("Pear"));
    // FruitMap == [
    //  { Key:5, Value:"Banana"    },
    //  { Key:2, Value:"Pear"      },
    //  { Key:7, Value:"Pineapple" }
    // ]
    
    FruitMap.Add(4);
    // FruitMap == [
    //  { Key:5, Value:"Banana"    },
    //  { Key:2, Value:"Pear"      },
    //  { Key:7, Value:"Pineapple" },
    //  { Key:4, Value:""          }
    // ]
    
  • Emplace 可以代替 Add,防止插入映射时创建临时文件

    FruitMap.Emplace(3, TEXT("Orange"));
    // FruitMap == [
    //  { Key:5, Value:"Banana"    },
    //  { Key:2, Value:"Pear"      },
    //  { Key:7, Value:"Pineapple" },
    //  { Key:4, Value:""          },
    //  { Key:3, Value:"Orange"    }
    // ]
    
  • Append 函数合并映射,将一个映射的所有元素移至另一个映射,源映射的相同键会替代目标映射中的键

    TMap<int32, FString> FruitMap2;
    FruitMap2.Emplace(4, TEXT("Kiwi"));
    FruitMap2.Emplace(9, TEXT("Melon"));
    FruitMap2.Emplace(5, TEXT("Mango"));
    FruitMap.Append(FruitMap2);
    // FruitMap == [
    //  { Key:5, Value:"Mango"     },
    //  { Key:2, Value:"Pear"      },
    //  { Key:7, Value:"Pineapple" },
    //  { Key:4, Value:"Kiwi"      },
    //  { Key:3, Value:"Orange"    },
    //  { Key:9, Value:"Melon"     }
    // ]
    // FruitMap2 is now empty.
    

迭代

  • 范围 - for

    for (auto& Elem :FruitMap)
    {
        FPlatformMisc::LocalPrint( *FString::Printf(TEXT("(%d, \"%s\")\n"), Elem.Key, *Elem.Value)  );
    }
    // Output:
    // (5, "Mango")
    // (2, "Pear")
    // (7, "Pineapple")
    // (4, "Kiwi")
    // (3, "Orange")
    // (9, "Melon")
    
  • 迭代器

    • CreateIterator 返回拥有读写访问权限的迭代器,
    • CreateConstIterator 返回拥有只读访问权限的迭代器
    for (auto It = FruitMap.CreateConstIterator(); It; ++It)
    {
        FPlatformMisc::LocalPrint(
            *FString::Printf( TEXT("(%d, \"%s\")\n"),
                It.Key(),   // same as It->Key
                *It.Value() // same as *It->Value
            ) );
    }
    

查询

  • Num 函数查询映射中保存的元素数量

    int32 Count = FruitMap.Num(); // Count == 6
    
  • Contains 函数查询是否包含特定键

    bool bHas7 = FruitMap.Contains(7); // bHas7 == true
    bool bHas8 = FruitMap.Contains(8); // bHas8 == false
    
  • [] 运算符将键用作索引查找相应值

    • 使用非常量映射执行该操作将返回非常量引用,使用常量映射将返回常量引用。
    • 在使用 运算符[] 前,应检查映射中是否存在该键。如果映射中键不存在,将触发断言。
    FString Val7 = FruitMap[7]; // Val7 == "Pineapple"
    FString Val8 = FruitMap[8]; // Assert!
    
  • Find 函数进行键查找

    • 如果映射包含该键,Find 将返回指向元素数值的指针;如果映射不包含该键,则返回null。
    • 在常量映射上调用 Find,返回的指针也将为常量。
    FString* Ptr7 = FruitMap.Find(7); // *Ptr7 == "Pineapple"
    FString* Ptr8 = FruitMap.Find(8); //  Ptr8 == nullptr
    
  • FindOrAdd 将返回对与给定键关联的值的引用。

    • 如果映射中不存在该键,FindOrAdd 将返回新创建的元素(使用给定键和默认构建值),该元素也会被添加到映射。
    • FindOrAdd 可修改映射,因此仅适用于非常量映射。
    FString& Ref7 = FruitMap.FindOrAdd(7);
    // Ref7     == "Pineapple"
    // FruitMap == [
    //  { Key:5, Value:"Mango"     },
    //  { Key:2, Value:"Pear"      },
    //  { Key:7, Value:"Pineapple" },
    //  { Key:4, Value:"Kiwi"      },
    //  { Key:3, Value:"Orange"    },
    //  { Key:9, Value:"Melon"     }
    // ]
    
    FString& Ref8 = FruitMap.FindOrAdd(8);
    // Ref8     == ""
    // FruitMap == [
    //  { Key:5, Value:"Mango"     },
    //  { Key:2, Value:"Pear"      },
    //  { Key:7, Value:"Pineapple" },
    //  { Key:4, Value:"Kiwi"      },
    //  { Key:3, Value:"Orange"    },
    //  { Key:9, Value:"Melon"     },
    //  { Key:8, Value:""          }
    // ]
    //如已发生重新分配,此处的 Ref7 引用可能会因 FruitMap.FindOrAdd(8) 的调用而无效化。
    
  • FindRef 会返回与给定键关联的值副本

    • 若映射中未找到给定键,则返回默认构建值。
    • FindRef 不会创建新元素,因此既可用于常量映射,也可用于非常量映射。
    FString Val7 = FruitMap.FindRef(7);
    FString Val6 = FruitMap.FindRef(6);
    // Val7     == "Pineapple"
    // Val6     == ""
    // FruitMap == [
    //  { Key:5, Value:"Mango"     },
    //  { Key:2, Value:"Pear"      },
    //  { Key:7, Value:"Pineapple" },
    //  { Key:4, Value:"Kiwi"      },
    //  { Key:3, Value:"Orange"    },
    //  { Key:9, Value:"Melon"     },
    //  { Key:8, Value:""          }
    // ]
    
  • FindKey 函数执行逆向查找

    • 返回指向与所提供值配对的第一个键的指针。搜索映射中不存在的值将返回空键。
    • 按值查找比按键查找慢(线性时间)。这是因为映射按键排序,而非按值排序。
    • 如果映射有多个具有相同值的键,FindKey 可返回其中任一键。
    const int32* KeyMangoPtr   = FruitMap.FindKey(TEXT("Mango"));   // *KeyMangoPtr   == 5
    const int32* KeyKumquatPtr = FruitMap.FindKey(TEXT("Kumquat")); //  KeyKumquatPtr == nullptr
    
  • GenerateKeyArray 和 GenerateValueArray 分别使用所有键和值的副本来填充 TArray。

    • 在这两种情况下,都会在填充前清空所传递的数组,因此产生的元素数量始终等于映射中的元素数量
    TArray<int32>   FruitKeys;
    TArray<FString> FruitValues;
    FruitKeys.Add(999);
    FruitKeys.Add(123);
    FruitMap.GenerateKeyArray  (FruitKeys);
    FruitMap.GenerateValueArray(FruitValues);
    // FruitKeys   == [ 5,2,7,4,3,9,8 ]
    // FruitValues == [ "Mango","Pear","Pineapple","Kiwi","Orange","Melon","" ]
    

移除

  • Remove 函数提供要移除元素的键。

    • 返回值是被移除元素的数量。
    • 如果映射不包含与键匹配的元素,则返回值可为零。
    • 移除元素将在数据结构(在Visual Studio的观察窗口中可视化映射时可看到)中留下空位
    FruitMap.Remove(8);
    // FruitMap == [
    //  { Key:5, Value:"Mango"     },
    //  { Key:2, Value:"Pear"      },
    //  { Key:7, Value:"Pineapple" },
    //  { Key:4, Value:"Kiwi"      },
    //  { Key:3, Value:"Orange"    },
    //  { Key:9, Value:"Melon"     }
    // ]
    
  • FindAndRemoveChecked 函数可用于从映射移除元素并返回其值。

    • 名称中的checked部分意味着将检查键是否存在,如果不存在,则出现断言:
    FString Removed7 = FruitMap.FindAndRemoveChecked(7);
    // Removed7 == "Pineapple"
    // FruitMap == [
    //  { Key:5, Value:"Mango"  },
    //  { Key:2, Value:"Pear"   },
    //  { Key:4, Value:"Kiwi"   },
    //  { Key:3, Value:"Orange" },
    //  { Key:9, Value:"Melon"  }
    // ]
    
    FString Removed8 = FruitMap.FindAndRemoveChecked(8);
    // Assert!
    
  • RemoveAndCopyValue 函数的作用与 Remove 相似,不同点是会将已移除元素的值复制到引用参数。如果映射中不存在指定的键,则输出参数将保持不变,函数将返回 false。

    FString Removed;
    bool bFound2 = FruitMap.RemoveAndCopyValue(2, Removed);
    // bFound2  == true
    // Removed  == "Pear"
    // FruitMap == [
    //  { Key:5, Value:"Mango"  },
    //  { Key:4, Value:"Kiwi"   },
    //  { Key:3, Value:"Orange" },
    //  { Key:9, Value:"Melon"  } ]
    
    bool bFound8 = FruitMap.RemoveAndCopyValue(8, Removed);
    // bFound8  == false
    // Removed  == "Pear", i.e. unchanged
    // FruitMap == [
    //  { Key:5, Value:"Mango"  },
    //  { Key:4, Value:"Kiwi"   },
    //  { Key:3, Value:"Orange" },
    //  { Key:9, Value:"Melon"  } ]
    
  • Empty 或 Reset 函数可将映射中的所有元素移除。

    • Empty 可采用参数指示映射中保留的slack量
    • Reset 则是尽可能多地留出slack量。
    TMap<int32, FString> FruitMapCopy = FruitMap;
    // FruitMapCopy == [
    //  { Key:5, Value:"Mango"  },
    //  { Key:4, Value:"Kiwi"   },
    //  { Key:3, Value:"Orange" },
    //  { Key:9, Value:"Melon"  }
    // ]
    
    FruitMapCopy.Empty();       // We could also have called Reset() here.
    // FruitMapCopy == []
    

排序

TMap 可以进行排序。排序后,迭代映射会以排序的顺序显示元素,但下次修改映射时,排序可能会发生变化。排序是不稳定的,因此等值元素在MultiMap中可能以任何顺序出现。

  • 使用 KeySort 或 ValueSort 函数可分别按键和值进行排序。两个函数均使用二元谓词来进行排序:

    FruitMap.KeySort([](int32 A, int32 B) {
        return A > B; // sort keys in reverse
    });
    // FruitMap == [
    //  { Key:9, Value:"Melon"  },
    //  { Key:5, Value:"Mango"  },
    //  { Key:4, Value:"Kiwi"   },
    //  { Key:3, Value:"Orange" }
    // ]
    
    FruitMap.ValueSort([](const FString& A, const FString& B) {
        return A.Len() < B.Len(); // sort strings by length
    });
    // FruitMap == [
    //  { Key:4, Value:"Kiwi"   },
    //  { Key:5, Value:"Mango"  },
    //  { Key:9, Value:"Melon"  },
    //  { Key:3, Value:"Orange" }
    // ]
    

运算符

  • 和 TArray 一样,TMap 是常规值类型,可通过标准复制构造函数或赋值运算符进行复制。因为映射严格拥有其元素,复制映射的操作是深层的,所以新的映射将拥有其自己的元素副本。

    TMap<int32, FString> NewMap = FruitMap;
    NewMap[5] = "Apple";
    NewMap.Remove(3);
    // FruitMap == [
    //  { Key:4, Value:"Kiwi"   },
    //  { Key:5, Value:"Mango"  },
    //  { Key:9, Value:"Melon"  },
    //  { Key:3, Value:"Orange" }
    // ]
    // NewMap == [
    //  { Key:4, Value:"Kiwi"  },
    //  { Key:5, Value:"Apple" },
    //  { Key:9, Value:"Melon" }
    // ]
    
  • MoveTemp 函数可调用移动语义。在移动后,源映射必定为空

    FruitMap = MoveTemp(NewMap);
    // FruitMap == [
    //  { Key:4, Value:"Kiwi"  },
    //  { Key:5, Value:"Apple" },
    //  { Key:9, Value:"Melon" }
    // ]
    // NewMap == []
    

Slack

简介

  • Slack是不包含元素的已分配内存。调用 Reserve 可分配内存,无需添加元素;
  • 通过非零slack参数调用 Reset 或 Empty 可移除元素,无需将其使用的内存取消分配。
  • Slack优化了将新元素添加到映射的过程,因为可以使用预先分配的内存,而不必分配新内存。
  • 它在移除元素时也十分实用,因为系统不需要将内存取消分配。在清空希望用相同或更少的元素立即重新填充的映射时,此方法尤其有效。
  • TMap 不像 TArray 中的 Max 函数那样可以检查预分配元素的数量。

函数

  • Reset

    FruitMap.Reset();
    // FruitMap == [<invalid>, <invalid>, <invalid>]
    // 内存不释放
    
  • Reserve 函数预先分配映射

    FruitMap.Reserve(10);
    for (int32 i = 0; i < 10; ++i)
    {
        FruitMap.Add(i, FString::Printf(TEXT("Fruit%d"), i));
    }
    // FruitMap == [
    //  { Key:9, Value:"Fruit9" },
    //  { Key:8, Value:"Fruit8" },
    //  ...
    //  { Key:1, Value:"Fruit1" },
    //  { Key:0, Value:"Fruit0" }
    // ]
    
    
  • 使用 Collapse 和 Shrink 函数可移除 TMap 中的全部slack。

    • Shrink 将从容器的末端移除所有slack,但这会在中间或开始处留下空白元素。
    for (int32 i = 0; i < 10; i += 2)
    {
        FruitMap.Remove(i);
    }
    // FruitMap == [
    //  { Key:9, Value:"Fruit9" },
    //  <invalid>,
    //  { Key:7, Value:"Fruit7" },
    //  <invalid>,
    //  { Key:5, Value:"Fruit5" },
    //  <invalid>,
    //  { Key:3, Value:"Fruit3" },
    //  <invalid>,
    //  { Key:1, Value:"Fruit1" },
    //  <invalid>
    // ]
    FruitMap.Shrink();
    // FruitMap == [
    //  { Key:9, Value:"Fruit9" },
    //  <invalid>,
    //  { Key:7, Value:"Fruit7" },
    //  <invalid>,
    //  { Key:5, Value:"Fruit5" },
    //  <invalid>,
    //  { Key:3, Value:"Fruit3" },
    //  <invalid>,
    //  { Key:1, Value:"Fruit1" }
    // ]
    //代码中,Shrink 只删除了一个无效元素,因为末端只有一个空元素
    
  • Compact 函数将空白空间组合在一起,为调用 Shrink 做好准备

    FruitMap.Compact();
    // FruitMap == [
    //  { Key:9, Value:"Fruit9" },
    //  { Key:7, Value:"Fruit7" },
    //  { Key:5, Value:"Fruit5" },
    //  { Key:3, Value:"Fruit3" },
    //  { Key:1, Value:"Fruit1" },
    //  <invalid>,
    //  <invalid>,
    //  <invalid>,
    //  <invalid>
    // ]
    FruitMap.Shrink();
    // FruitMap == [
    //  { Key:9, Value:"Fruit9" },
    //  { Key:7, Value:"Fruit7" },
    //  { Key:5, Value:"Fruit5" },
    //  { Key:3, Value:"Fruit3" },
    //  { Key:1, Value:"Fruit1" }
    // ]
    

KeyFuncs

只要类型具有 运算符== 和非成员 GetTypeHash 重载,就可用作 TMap 的KeyType,不需要任何更改。但是,可能需要将类型用作键,而不重载这些函数。在这些情况下,可对 KeyFuncs 进行自定义。为键类型创建 KeyFuncs,必须定义两个typedef和三个静态函数,如下所示:

  • KeyInitType —— 用于传递键的类型。
  • ElementInitType —— 用于传递元素的类型。
  • KeyInitType GetSetKey(ElementInitType Element)——返回元素的键。
  • bool Matches(KeyInitType A, KeyInitType B) —— 如果 A 和 B 等值将返回 true,否则返回 false
  • uint32 GetKeyHash(KeyInitType Key) —— 返回 Key 的散列值。

KeyInitType 和 ElementInitType 是键类型和值类型的常规传递约定的typedef。它们通常为浅显类型的一个值,和非浅显类型的一个常量引用。请记住,映射的元素类型是 TPair

struct FMyStruct
{
    // String which identifies our key
    FString UniqueID;

    // Some state which doesn't affect struct identity
    float SomeFloat;

    explicit FMyStruct(float InFloat)
        :UniqueID (FGuid::NewGuid().ToString())
        , SomeFloat(InFloat)
    {
    }
};
template <typename ValueType>
struct TMyStructMapKeyFuncs :
    BaseKeyFuncs<
        TPair<FMyStruct, ValueType>,
        FString
    >
{
private:
    typedef BaseKeyFuncs<
        TPair<FMyStruct, ValueType>,
        FString
    > Super;

public:
    typedef typename Super::ElementInitType ElementInitType;
    typedef typename Super::KeyInitType     KeyInitType;

    static KeyInitType GetSetKey(ElementInitType Element)
    {
        return Element.Key.UniqueID;
    }

    static bool Matches(KeyInitType A, KeyInitType B)
    {
        return A.Compare(B, ESearchCase::CaseSensitive) == 0;
    }

    static uint32 GetKeyHash(KeyInitType Key)
    {
        return FCrc::StrCrc32(*Key);
    }
};

FMyStruct 具有唯一标识符,以及一些与身份无关的其他数据。GetTypeHash 和 运算符== 不适用于此,因为 运算符== 为实现通用目的不应忽略任何类型的数据,但同时又需要如此才能与 GetTypeHash 的行为保持一致,后者只关注 UniqueID 字段。以下步骤有助于为 FMyStruct 创建自定义 KeyFuncs

  1. 首先,继承 BaseKeyFuncs,因为它可以帮助定义某些类型,包括 KeyInitType 和 ElementInitType

    BaseKeyFuncs 使用两个模板参数:映射的元素类型和键类型。和所有映射一样,元素类型是 TPair,使用 FMyStruct 作为其 KeyTypeTMyStructMapKeyFuncs 的模板参数作为其 ValueType。将备用 KeyFuncs 用作模板,可为每个映射指定 ValueType,因此每次要在 FMyStruct 上创建键控 TMap 时不必定义新的 KeyFuncs。第二个 BaseKeyFuncs 参数是键类型,不要与元素存储的键区(TPair 的 KeyType)混淆。因为此映射应使用 UniqueID(来自 FMyStruct)作为键,所以此处使用 FString

  2. 然后,定义三个必需的 KeyFuncs 静态函数。第一个是 GetSetKey,该函数返回给定元素类型的键。由于元素类型是 TPair,而键是 UniqueID,所以该函数可直接返回 UniqueID

    第二个静态函数是 Matches,该函数接受两个元素的键(由 GetSetKey 获取),然后比较它们是否相等。在 FString 中,标准的等效测试(运算符==)不区分大小写;要替换为区分大小写的搜索,请用相应的大小写对比选项使用 Compare 函数。

  3. 最后,GetKeyHash 静态函数接受提取的键并返回其散列值。由于 Matches 函数区分大小写,GetKeyHash 也必须区分大小写。区分大小写的 FCrc 函数将计算键字符串的散列值。

  4. 现在结构已满足 TMap 要求的行为,可创建它的实例。

TMap<
        FMyStruct,
        int32,
        FDefaultSetAllocator,
        TMyStructMapKeyFuncs<int32>
    > MyMapToInt32;

    // Add some elements
    MyMapToInt32.Add(FMyStruct(3.14f), 5);
    MyMapToInt32.Add(FMyStruct(1.23f), 2);

    // MyMapToInt32 == [
    //  {
    //      Key:{
    //          UniqueID:"D06AABBA466CAA4EB62D2F97936274E4",
    //          SomeFloat:3.14f
    //      },
    //      Value:5
    //  },
    //  {
    //      Key:{
    //          UniqueID:"0661218447650259FD4E33AD6C9C5DCB",
    //          SomeFloat:1.23f
    //      },
    //      Value:5
    //  }
    // ]

在自行设置KeyFuncs时,要注意 TMap 假设两个项目使用 Matches 比较的结果相等,则它们会从 GetKeyHash 返回相同的值。此外,如果对现有映射元素的键进行的修改将会改变来自这两个函数中任一个的结果,那么系统会将这种修改视作未定义的行为,因为这会使映射的内部散列失效。这些规则也适用于使用默认 KeyFuncs 时 运算符== 和 GetKeyHash 的重载。

其他

CountBytes 和 GetAllocatedSize 函数用于估计内部数组的当前内存使用情况。CountBytes 接受 Farchive 参数,而 GetAllocatedSize 则不会。这些函数常用于统计报告。

Dump 函数接受 FOutputDevice,并写出关于映射内容的实现信息。此函数常用于调试。

参考

TMap

<think>好的,我现在需要解释TArray<TSharedPtr<FJsonValue>>这个类型。首先,我应该拆解每个部分,看看用户可能对哪些部分不熟悉。 TArray是Unreal Engine里的动态数组模板类,对吧?用户可能知道C++的std::vector,但TArray是UE自己实现的,功能类似但可能有不同的方法或优化。需要简单说明TArray的作用,比如动态扩容、内存管理等。 然后是TSharedPtr,这是UE的智能指针,基于引用计数。用户可能了解std::shared_ptr,但UE有自己的实现,比如线程安全或者和UE其他系统的集成。需要解释TSharedPtr的作用,比如自动管理对象生命周期,避免内存泄漏。 接着是FJsonValue,这是UE的JSON库中的类,用来表示JSON中的各种值类型,比如字符串、数字、对象、数组等。需要说明FJsonValue的作用,以及为什么用指针来存储,可能是因为多态性,不同的子类处理不同类型的数据。 组合起来,TArray<TSharedPtr<FJsonValue>>就是一个动态数组,每个元素都是指向FJsonValue的共享指针。这样的结构在解析或构建JSON数据时常用,比如处理JSON数组中的多个元素,每个元素可以是不同类型,通过智能指针管理内存,避免手动释放。 用户可能想知道为什么用TSharedPtr而不是原始指针,或者裸指针。这时候需要强调内存安全,智能指针自动释放资源,避免内存泄漏。另外,可能UE的JSON API要求使用这种指针,比如返回或接收智能指针类型的对象。 可能还需要举例说明,比如解析一个JSON数组,每个元素都被包装成FJsonValue的共享指针,并存储在TArray中。或者构建这样的数组,添加各种类型的FJsonValue对象,然后序列化成JSON字符串。 另外,可能需要注意UE版本差异,但用户没有提到特定版本,所以假设是最新的稳定版。如果有常见的错误或使用注意事项,比如循环引用导致的内存泄漏,或者线程安全问题,但TSharedPtr是否是线程安全的?需要确认UE的文档,默认情况下可能不是,但用户可能需要知道这点。 总结结构的话,先分解每个模板参数,解释各自的作用,然后整体说明这个类型的用途和典型使用场景。可能还需要提到相关的其他类,比如FJsonObject、FJsonSerializer等,说明如何配合使用。</think>TArray<TSharedPtr<FJsonValue>> 是虚幻引擎(Unreal Engine)中用于处理JSON数据的复合类型。以下逐步解析其结构和用途: --- ### 1. **分解类型结构** #### (a) `TArray<T>` - **作用**:虚幻引擎的动态数组模板类(类似C++的`std::vector`)。 - **特点**: - 自动管理内存分配和扩容。 - 提供增删查改等常用操作(如`Add()`, `RemoveAt()`, `Find()`)。 - 性能优化,与UE其他容器(如`TMap`)深度集成。 #### (b) `TSharedPtr<T>` - **作用**:虚幻引擎的共享指针(类似C++的`std::shared_ptr`)。 - **特点**: - 基于引用计数自动管理对象生命周期。 - 线程安全(引用计数操作通过原子操作保证)。 - 支持自定义删除器(Deleter),适应特定资源释放逻辑。 #### (c) `FJsonValue` - **作用**:UE的JSON库(`Json`模块)中表示JSON值的基类。 - **派生类**: - `FJsonValueString`:字符串类型。 - `FJsonValueNumber`:数值类型。 - `FJsonValueBoolean`:布尔类型。 - `FJsonValueObject`:嵌套的JSON对象(`TSharedPtr<FJsonObject>`)。 - `FJsonValueArray`:嵌套的JSON数组(`TArray<TSharedPtr<FJsonValue>>`)。 - **多态性**:通过指针实现不同类型值的统一存储。 --- ### 2. **组合后的意义** `TArray<TSharedPtr<FJsonValue>>` 表示一个**动态数组**,其中每个元素是: - 一个指向`FJsonValue`的共享指针。 - 可安全存储任意JSON类型(字符串、数字、对象、数组等)。 - 自动管理内存,避免手动释放资源。 --- ### 3. **典型应用场景** #### (a) **解析JSON数组** 假设JSON数据如下: ```json [42, "Hello", {"Key": "Value"}] ``` 解析后的内存结构: $$ \text{TArray}[ \;\; \text{TSharedPtr}(FJsonValueNumber(42)), \;\; \text{TSharedPtr}(FJsonValueString("Hello")), \;\; \text{TSharedPtr}(FJsonValueObject(...)) \;\; ] $$ #### (b) **构建JSON数组** ```cpp TArray<TSharedPtr<FJsonValue>> JsonArray; // 添加数值 JsonArray.Add(MakeShared<FJsonValueNumber>(42)); // 添加字符串 JsonArray.Add(MakeShared<FJsonValueString>("Hello")); // 序列化为JSON字符串 FString OutputString; TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString); FJsonSerializer::Serialize(JsonArray, Writer); ``` #### (c) **遍历与类型检查** ```cpp for (const TSharedPtr<FJsonValue>& Value : JsonArray) { if (Value->Type == EJson::String) { FString StrValue = Value->AsString(); } else if (Value->Type == EJson::Number) { double NumValue = Value->AsNumber(); } } ``` --- ### 4. **注意事项** - **类型安全**:需通过`FJsonValue::Type`字段显式检查实际类型,避免错误转换。 - **循环引用**:若JSON对象间存在循环引用,需手动打破引用或使用弱指针(`TWeakPtr`)。 - **性能**:频繁操作大型数组时,优先使用`TArray`预分配(`Reserve()`)减少扩容开销。 --- ### 5. **相关类型** - `FJsonObject`:表示JSON对象,存储键值对(`TMap<FString, TSharedPtr<FJsonValue>>`)。 - `FJsonSerializer`:提供JSON序列化与反序列化方法(如`Serialize()`/`Deserialize()`)。 如需进一步探讨具体操作(如解析嵌套对象),可提供示例代码说明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值