Unreal C++中如何防止循环内的Object对象被意外销毁?
Unreal C++ GeoJSON解析对象存活问题修复方案
问题根因
你遇到的parent address is invalid错误,本质是FJsonObject共享指针的引用计数提前归零导致对象被回收,核心原因有三个:
- 默认
FJsonSerializer反序列化生成的FJsonObject,其生命周期绑定在父级FJsonValue(也就是RawJsonValueArray里的元素)上,如果后续操作意外修改了父级数组内容,或者共享指针的引用被提前释放,子对象就会被销毁 - 当前代码没有做非空校验,如果对应索引的
FJsonValue不是Object类型、或者目标字段不存在,GetObjectField/GetStringField会触发不可预期的内存访问错误 - 标准GeoJSON的顶层结构通常是Object而不是Array,如果反序列化的目标和实际Json结构不匹配,也会导致解析出来的对象结构异常。
修复方案
按照以下步骤调整代码即可保证GeoJsonObject的存活状态:
- 把
GeoJsonObject的声明移到循环内部,避免跨循环迭代的指针复用导致的异常 - 新增非空校验,确认所有指针有效后再访问字段
- 如果需要在循环外长期持有解析结果,单独用数组存储
FJsonObject的共享指针,主动增加引用计数 - 如果你的GeoJSON顶层是单个Object,需要把反序列化的输出参数从数组改成单个
FJsonObject共享指针
修正后的参考代码
void test::ParseGeoJson(const FString& File) { FString JsonString; if (!FFileHelper::LoadFileToString(JsonString, *File)) { UE_LOG(LogTemp, Error, TEXT("加载Json文件失败:%s"), *File); return; } // 如果确认你的GeoJSON顶层是数组,保留以下反序列化逻辑 TArray<TSharedPtr<FJsonValue>> RawJsonValueArray; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(*JsonString); bool success = FJsonSerializer::Deserialize(Reader, RawJsonValueArray); // 如果你的GeoJSON顶层是Object,用下面的代码替换上面的反序列化逻辑 // TSharedPtr<FJsonObject> RootObject; // TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(*JsonString); // bool success = FJsonSerializer::Deserialize(Reader, RootObject); if (success && RawJsonValueArray.Num() > 0) { // 如果你需要长期持有所有解析后的对象,定义这个数组来存,主动加引用计数 TArray<TSharedPtr<FJsonObject>> AllParsedGeoObjects; for (int32 index = 0; index < RawJsonValueArray.Num(); index++) { // 先检查当前Json值是不是Object类型 if (!RawJsonValueArray[index]->IsObject()) { continue; } // 把声明移到循环内部 TSharedPtr<FJsonObject> GeoJsonObject = RawJsonValueArray[index]->AsObject(); if (!GeoJsonObject.IsValid()) { continue; } // 读取字段前先判断字段是否存在 TSharedPtr<FJsonObject> geometry = nullptr; if (GeoJsonObject->HasTypedField<EJson::Object>("geometry")) { geometry = GeoJsonObject->GetObjectField("geometry"); } FString type; if (GeoJsonObject->HasTypedField<EJson::String>("type")) { type = GeoJsonObject->GetStringField("type"); } // 其余字段读取也加同样的校验逻辑 TSharedPtr<FJsonObject> properties = GeoJsonObject->HasTypedField<EJson::Object>("properties") ? GeoJsonObject->GetObjectField("properties") : nullptr; TSharedPtr<FJsonObject> layer = GeoJsonObject->HasTypedField<EJson::Object>("layer") ? GeoJsonObject->GetObjectField("layer") : nullptr; FString source = GeoJsonObject->HasTypedField<EJson::String>("source") ? GeoJsonObject->GetStringField("source") : TEXT(""); // 如果需要长期持有,加入数组,引用计数+1,对象就不会被提前释放 AllParsedGeoObjects.Add(GeoJsonObject); } // 后续可以正常使用AllParsedGeoObjects里的对象 } }
额外建议
如果不需要动态读取Json字段,建议解析完成后直接把需要的属性转成自定义结构体存储,不要长期持有FJsonObject指针,能彻底避免共享指针生命周期管理带来的野指针问题。
内容的提问来源于stack exchange,提问作者dan
相关产品推荐
相关产品推荐

