How exactly does `Transient` work?

Hi,

How exactly does `Transient` work?

Default initialization confusion:

I thought every `UPROPERTY()`—even with no flags—is always zero-initialized at object construction. However, in PIE sessions, I have an actor filling a UPROPERTY array on `BeginPlay()`. Sometimes after restarting the match (same world instance reused), this array still contains old elements instead of clearing.

I’m specifically referring to plain `UPROPERTY()` vs `UPROPERTY(Transient)`, not properties edited or overridden via Blueprint defaults. Why might this happen?

Marking properties as `Transient`:

Some devs recommend marking everything possible as `Transient` if it doesn’t require serialization. Is this actually a good practice officially? Does it provide real benefits (memory savings, cook-time reduction), or is it only a defensive measure? Are there any downsides?

Engine code to study:

Which engine source files/functions best demonstrate:

- Zero-initialization logic for plain UPROPERTYs.

- How exactly `Transient` properties are skipped during serialization.

- Interaction between `Transient`, GC, and PIE world resets.

For example, I have a UWorldSubsystem class with an array of Actors that gets filled during gameplay:

UPROPERTY() TArray<TObjectPtr<AActor>> MyActors;Should I explicitly mark `MyActors` as `Transient`, or does it not make sense in this context?

Thanks in advance for clarifying!

— Kirill Mintsev

Hello there,

I’ll cover briefly how UPROPERTY(Transient) is handled first, since tracking it through the codebase is easier from there. The handling of all UPROPERTY specificers, and all similar macros, is performed by UnrealHeaderTool. This code is housed in the EpicGames.UHT project. The result of UHT handling UPROPERTY(Transient) code sets CPF_Transient on the property flags. The CPF_Transient flag can be searched against to find the uses of transient properties in the C++ codebase. It’s worth noting that Transient on a UClass leads to a different flag that is handled differently.

Transient is defined as being zero-filled at load-time and non-serialised. It also means it doesn’t get copied between actors in editor utilities like CopyActorProperties. There are a few exceptions to this definition, though.

The code that skips Transient serialisation can be found in CoreUObject/Private/UObject/Property.cpp in the function FProperty::ShouldSerializeValue(). There is also ShouldSkipProperty in ActorComponent.cpp that does something similar.

In terms of default initialisation, it appears that that becomes reflected end up at FObjectInitializer::InitProperties via the UHT.

I’m not sure about marking everything Transient unless the variables are actually transient. Transient will prevent non-CDO instances of a class from serialising that property into uassets, so if the class is used a lot as an instance in a level, it may be worth it to reduce both the disk usage and the load time. If a property value can be set on the instance, it needs to not be Transient.

Transient shouldn’t affect save game serialisation: uproperties are ignored unless specifically requested with UPROPERTY(SaveGame) or manually serialised in an overriden Serialise() function.

In your case, Transient would prevent the array from being serialised to disk, which would seem like the correct behaviour for this array. UWorldMetricsSubsystem, for example, marks the Metrics array as Transient to prevent it from being serialised. For completeness, in this case, Transient is likely just a safety measure as I don’t believe most UWorldSubsystems get serialised.

If the same world instance can be reused, it is probably worth clearing the array in EndPlay(). Transient won’t affect this as the object isn’t being loaded.

I hope that helps.

Best regards,

Chris

Hello Chris Anderson,

Thank you very much for your assistance. I will carefully review the code you mentioned.

With appreciation,

Kirill

No worries, you’re very welcome.

If you have any questions, please feel free to ask. Otherwise, would you mind if I close this case for now?

Best regards,

Chris