The Question
Is it safe to use UPROPERTY on a private member or do they have to be protected instead?
For some reason this is a problem that kept biting me when I first started writing Unreal C++. I think the confusion was around the visibility specifiers duplicating the specifiers already present in C++ (public, private, protected).
The Core Issues
Apparently I’m not the only one as I found a few posts on the unreal engine forum curious about the same topic. Let’s lay out the core issues:
- You should always define variables based on the principle of least visibility
- You sometimes (but not always) want to expose C++ variables to Blueprints
- Confusing these specifiers will run in to editor weirdness that is often hard to troubleshoot: half configured components and missing editor windows
C++ Specifiers
It’s important to remember that the C++ language specifiers work in tandem with the UPROPERTY specifiers, but they are distinct. You can expose a C++ private variable to blueprint under certain circumstances.
Private
UPROPERTY cannot be private. Though they can be protected, and protected properties are accessible across all blueprints.
Note: There is a meta specifiers of AllowPrivateAccess = true which will prevent warnings when exposing a private variable to a blueprint, but we shouldn’t consider this a best practice.
Protected
Variables that are declared as protected can not be marked UPROPERTY(BlueprintReadWrite) or the build chain will complain. These will need to be marked public to also be BlueprintReadWrite
Public
If you wish to make a UPROPERTY with the BlueprintReadWrite it’s visibility must be public.
Blueprint Visibility Specifiers
These specifiers are only concerned with whether blueprint can read or update the variables backed by C++
BlueprintReadWrite / BlueprintReadOnly
BlueprintReadWrite- This property can be read or written from a Blueprint. This Specifier is incompatible with theBlueprintReadOnlySpecifier.BlueprintReadOnly- This property can be read by Blueprints, but not modified. This Specifier is incompatible with theBlueprintReadWriteSpecifier.
BlueprintSetter / BlueprintGetter
In addition to exposing the property directly, we could instead provide access to blueprint via setters and getters:
BlueprintGetter=GetterFunctionName- This property specifies a custom accessor function. If this property isn’t also tagged withBlueprintSetterorBlueprintReadWrite, then it is implicitlyBlueprintReadOnly.BlueprintSetter=SetterFunctionName- This property has a custom mutator function, and is implicitly tagged withBlueprintReadWrite. Note that the mutator function must be named and part of the same class.
Interestingly, if we apply these specifiers correctly we can get the behavior of the explicity read and write specifiers implicitly.
Blueprint Mutability Specifiers
These specifiers exist to serve two purposes that it’s important to keep in your mind when defining them.
- Whether the property should be editable or merely visible (read-only)
- Whether the panel should be visible everywhere, restricted to archetypes, or restricted to instances In this context when we say archetype we mean the class that defines the object. Some values make sense as a configuration for how a class behaves, while other values represent instance data that may change over time. Establishing this distinction will ensure you’re exposing values at the write level when defining your headers.
EditInstanceOnly / EditDefaultsOnly / EditAnywhere
- These specifiers are incompatible with any of the the
Visiblespecifiers. - Allows editing with the details panel in the editor
VisibleAnywhere / VisibleDefaultsOnly / VisibleInstanceOnly
- These specifiers are incompatible with any of the the
Editspecifiers. - Allows read-only access with the details panel in the editor
Declaring Owned Components
Owned components are a special case that you’ll want to remember:
private:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess=true))
private- to prevent outside classes from modifying (principle of least visibility)VisibleAnywhere- to ensure we can see the details anywhereBlueprintReadOnly- to ensure the pointer to the component isn’t changed, since we’ll manage it in the C++ constructorAllowPrivateAccess=true- normally you wouldn’t make a private variableBlueprintReadOnly, if you wanted it exposed to subclasses it would need to be markedprotected. The build tools will correctly complain about this. Adding themetasilences the warning for components.