Public, protected or private with AllowPrivateAccess - Best practices

Hi,

I know the meaning of these access modifiers and the concept of encapsulation. My general rule of thumb (also outside of Unreal) is to mark as…

  • private: As much as possible
  • protected: if it’s necessary to expose members of the base class to a child class
  • public: only getters/setters and the public interface

Therefore I make heavy use of meta=(AllowPrivateAccess = true). Is this a code smell? Are the any best practices?

Thanks for any hints!

1 Like

Seems a bit strange to me to routinely have stuff that’s inaccessible in C++ be accessible via BP?

1 Like

Seems a bit strange to me to routinely have stuff that’s inaccessible in C++ be accessible via BP?

Exactly. Actually there is a third option BlueprintGetter as discussed in a related thread https://forums.unrealengine.com/t/why-are-blueprintgetter-not-visible/579329.

What I usually see in tutorals is the AllowPrivateAccess option. However, to me this seems more like a workaround. Shouldn’t be made more heavy use of BlueprintGetter and BlueprintSetter? What is the best coding practice?

Only use getters if your getter needs to do some special checks or other procedures when getting the variable, or to protect the variable from being set.

Only use setters if you need to do special checks etc. when the variable is being set.

(That or the variable is accessed via an interface for which you need functions)

If you have a variable that needs to be accessed by other classes and there are no special checks or other things the class needs to do before allowing the other classes to modify / read the variable, just leave it public.

Don’t bloat the header files that is not needed. Doing encapsulation for the sake of encapsulation is a fool’s errand and you end up with more code, not less. Having public variable communicates more on its own than with getters and setters, it tells the programmer that no special checks are needed whatsoever. Encapsulation should not be used to make every class completely independent from each other by default. Sometimes a bunch of classes are needed together to form a set of functionality. If class A and class B are always going to be a present when the system they form is being used, then the connection may just as well be explicit. Only when different classes need to interact with each other but aren’t always necessarily present at the same time, you want to set up some sort of interface to carry that information.

BlueprintGetter / BlueprintSetter only allows you to add a special check before the variable is taken in the blueprint graph. If you need some specialized function to handle variables you could also make a custom getter as an UFUNCTION() and set is as BlueprintCallable. That way you could also do errorhandling, just like how the casting nodes have another execution output in case the cast fails.

I recommend reading on some of the source classes like ACharacter to see how their header files have been setup.

5 Likes

Thanks for the comprehensive answer! I also liked how’d you describe the BlueprintGetters/Setters as overrides in the beforementioned thread. I have the impression, that even though encapsulation concepts are quite the same among programming languages, they are still anticipated a bit differently (in C# people tend to encapsulate everything).

In Unreal it seems there is little hesitation in making properties public, if there is no additional access logic or need to hide it. I took a look into the ACharacter class (UE5.01). Just to get a feeling for it.

ACharacter:

  • Total number of lines of code for the class: 782
  • private: 19
  • protected: 56
  • public: 705

The 19 LoC for the private section are spend solely on USceneComponents and BlueprintReadOnly:

private:
	/** The main skeletal mesh associated with this Character (optional sub-object). */
	UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true"))
	TObjectPtr<USkeletalMeshComponent> Mesh;

	/** Movement component used for movement logic in various movement modes (walking, falling, etc), containing relevant settings and functions to control movement. */
	UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true"))
	TObjectPtr<UCharacterMovementComponent> CharacterMovement;

	/** The CapsuleComponent being used for movement collision (by CharacterMovement). Always treated as being vertically aligned in simple collision check functions. */
	UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true"))
	TObjectPtr<UCapsuleComponent> CapsuleComponent;

#if WITH_EDITORONLY_DATA
	/** Component shown in the editor only to indicate character facing */
	UPROPERTY()
	TObjectPtr<UArrowComponent> ArrowComponent;
#endif

Just for the information. Thanks, and bye!

1 Like

What I found in the offical and comprehensive C++ LyraStarterGame predominantly…

public:

  • No variables, only functions with UFUNCTION(BlueprintCallable or BlueprintPure)
  • Or delegates with UPROPERTY(BlueprintAssignable)

protected:

  • Often blank because Macros are inherited
  • Almost no variables only functions

private:

  • Components are always:
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Meta = (AllowPrivateAccess = "true"))
  • Variables are often UPROPERTY()
  • Exposed variables: UPROPERTY(BlueprintReadOnlly / EditInstanceOnly / ...)

That being said there seems to be almost no public variable except for delegates and GamplayTags.

1 Like