Polymorphism is a key component to object-oriented programming (OOP). OOP is a popular design choice in software development. Because OOP is frequently used, it is vital that you are familiar with the concept of polymorphism.
My goal in this post is to provide an overview of how polymorphism works in Unreal Engine 4 (UE4) C++ and provide an example putting the concept together.
Recommendation: Understand the concept of inheritance will be useful.
What Is Polymorphism?
The word polymorphism means having many forms. Usually, polymorphism occurs when there is a hierarchy of classes that are related by inheritance. Specifically, in C++ that means a call to a member function will cause a different function to run depending on the type of object that invokes the function.
Consider the following diagram as an example:
The base class has a member function called speak. The child classes are Cat and Dog and they have their own definition for speak. When a cat object calls the speak function “Meow” is the output, while a dog object calling the speak function, “Woof” is the output.
Why Use Polymorphism?
Polymorphism allows each child classes to have their own separate implementation for a function with the same name. This is advantageous because it makes the code more modular and eliminates the need to keep adding new functions.
For example, if you have ten different child classes of Animal, you would need a unique speak function name for each one. You may have “speakDog”, “speakCat”, “speakPig”, “speakCow”, and so on. However, with polymorphism, you can keep the function name the same and change the implementation of the function for each class.
Common Mistakes
When you are using polymorphism, make sure you are calling the correct function. Sounds obvious, but this can get tricky when you are using pointers. For example, if you have a Shape class pointer and it is pointing to a Rectangle class it is possible that the Shape class function runs instead.
This happens when the compiler sets which function to call during compilation time. This is called static resolution, static linkage, or early binding. To overcome this issue, you need to tell the compiler to decide which function to invoke at runtime by using the keyword “virtual.”
Virtual Functions
Adding the keyword “virtual” to a function header turns it into a virtual function. Virtual functions are used to make sure the child class invokes the correct function. What this means is that the compiler will no longer consider the function for static linking. Instead, which function gets invoke will be determined at runtime (dynamic linkage).
Pure Virtual Functions
There may be occasions where you want to include a virtual function in a base class, but there is no meaningful definition you can give it. In this type of scenario, you can make your virtual function in the base class into a pure virtual one. The purpose of the pure virtual function is for each derived class to redefine the function to suit their functionality, but serves no purpose in the base class. Pure virtual functions require that each child to define the function otherwise there will be a compilation error.
For example, consider the case of a Shape base class and a Cube child class. The getVolume function would not be meaningful in the Shape class, but you would want Cube to have its own implementation for obtaining the volume.
Examples
The following are three examples to demonstrate how UE4 C++ works with polymorphism. The scenario is that there is a base class ShapeActor and a child class CubeActor and the CubeActor prints out its volume after initialization.
For reference, I am using the following for the examples:
- UE4.13
- Visual Studio 2015 (community version)
No Virtual Function
UCLASS() class VRDEMO_API AMyShapeActor : public AActor { GENERATED_BODY() private: int my_secret_number = 42; protected: UPROPERTY(EditAnywhere, BlueprintReadWrite) int length = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int width = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int height = 0; public: // Sets default values for this actor's properties AMyShapeActor(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; UFUNCTION(BlueprintCallable, Category = "Size") void setLength(int new_length); UFUNCTION(BlueprintCallable, Category = "Size") void setWidth(int new_width); UFUNCTION(BlueprintCallable, Category = "Size") void setHeight(int new_height); int getVolume(); static FORCEINLINE void Print(FString text_to_print, bool print_to_log = false, FColor color = FColor::Green, float TimeToDisplay = 1.5f) { if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, TimeToDisplay, color, text_to_print); } if (print_to_log) { UE_LOG(LogTemp, Warning, TEXT("%s"), *text_to_print); } } };
#include "MyShapeActor.h" // Sets default values AMyShapeActor::AMyShapeActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; } // Called when the game starts or when spawned void AMyShapeActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AMyShapeActor::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } void AMyShapeActor::setLength(int new_length) { length = new_length; } void AMyShapeActor::setWidth(int new_width) { width = new_width; } void AMyShapeActor::setHeight(int new_height) { height = new_height; } int AMyShapeActor::getVolume() { Print("Hello from Parent class getVolume function", false, FColor::Red, 10.0f); return 0; }
UCLASS() class VRDEMO_API AMyCubeActor : public AMyShapeActor { GENERATED_BODY() private: UStaticMeshComponent* my_static_mesh; protected: public: AMyCubeActor(); int getVolume(); };
#include "MyCubeActor.h" AMyCubeActor::AMyCubeActor() { PrimaryActorTick.bCanEverTick = true; static ConstructorHelpers::FObjectFinder<UStaticMesh> static_mesh(TEXT("StaticMesh'/Game/Geometry/Meshes/1M_Cube.1M_Cube'")); my_static_mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("static_mesh")); my_static_mesh->SetStaticMesh(static_mesh.Object); my_static_mesh->SetWorldScale3D(FVector(0.3f, 0.3f, 0.3f)); RootComponent = my_static_mesh; length = 11; width = 11; height = 11; // this will result in static linkage so we will see the parent getVolume function run AMyShapeActor* myself = this; *myself.getVolume(); } int AMyCubeActor::getVolume() { int volume = length * width * height; FString msg("Hi from Cube Actor Child - C++\nlength = "); msg.AppendInt(length); msg.Append(" , width = "); msg.AppendInt(width); msg.Append(", height = "); msg.AppendInt(height); msg.Append("\nVolume = "); msg.AppendInt(volume); Print(msg, false, FColor::Green, 10.0f); return volume; }
This example demonstrates:
- Static linkage occurrences are presented as warning in Visual Studio IDE
- UE4 has a safe guard against static linkage. Static linkages are considered as errors, which causes compilation failure
Virtual Function
UCLASS() class VRDEMO_API AMyShapeActor : public AActor { GENERATED_BODY() private: int my_secret_number = 42; protected: UPROPERTY(EditAnywhere, BlueprintReadWrite) int length = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int width = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int height = 0; public: // Sets default values for this actor's properties AMyShapeActor(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; UFUNCTION(BlueprintCallable, Category = "Size") void setLength(int new_length); UFUNCTION(BlueprintCallable, Category = "Size") void setWidth(int new_width); UFUNCTION(BlueprintCallable, Category = "Size") void setHeight(int new_height); virtual int getVolume(); static FORCEINLINE void Print(FString text_to_print, bool print_to_log = false, FColor color = FColor::Green, float TimeToDisplay = 1.5f) { if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, TimeToDisplay, color, text_to_print); } if (print_to_log) { UE_LOG(LogTemp, Warning, TEXT("%s"), *text_to_print); } } };
#include "MyShapeActor.h" // Sets default values AMyShapeActor::AMyShapeActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; } // Called when the game starts or when spawned void AMyShapeActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AMyShapeActor::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } void AMyShapeActor::setLength(int new_length) { length = new_length; } void AMyShapeActor::setWidth(int new_width) { width = new_width; } void AMyShapeActor::setHeight(int new_height) { height = new_height; } int AMyShapeActor::getVolume() { Print("Hello from Parent class getVolume function", false, FColor::Red, 10.0f); return 0; }
UCLASS() class VRDEMO_API AMyCubeActor : public AMyShapeActor { GENERATED_BODY() private: UStaticMeshComponent* my_static_mesh; protected: public: AMyCubeActor(); int getVolume(); };
#include "MyCubeActor.h" AMyCubeActor::AMyCubeActor() { PrimaryActorTick.bCanEverTick = true; static ConstructorHelpers::FObjectFinder<UStaticMesh> static_mesh(TEXT("StaticMesh'/Game/Geometry/Meshes/1M_Cube.1M_Cube'")); my_static_mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("static_mesh")); my_static_mesh->SetStaticMesh(static_mesh.Object); my_static_mesh->SetWorldScale3D(FVector(0.3f, 0.3f, 0.3f)); RootComponent = my_static_mesh; length = 11; width = 11; height = 11; // get reference to ourself as the Parent class pointer and call the proper getVolume function // this is dynamic linkage, where the function to call is determine at runtime AMyShapeActor* myself = &(*this); myself->getVolume(); } int AMyCubeActor::getVolume() { int volume = length * width * height; FString msg("Hi from Cube Actor Child - C++\nlength = "); msg.AppendInt(length); msg.Append(" , width = "); msg.AppendInt(width); msg.Append(", height = "); msg.AppendInt(height); msg.Append("\nVolume = "); msg.AppendInt(volume); Print(msg, false, FColor::Green, 10.0f); return volume; }
This example demonstrates:
- UE4 C++ works like regular C++
- Dynamic linkage
- Without redefining virtual function, the parent’s function runs when the child calls the function
Pure Virtual Function
UCLASS(abstract) class VRDEMO_API AMyShapeActor : public AActor { GENERATED_BODY() private: int my_secret_number = 42; protected: UPROPERTY(EditAnywhere, BlueprintReadWrite) int length = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int width = 0; UPROPERTY(EditAnywhere, BlueprintReadWrite) int height = 0; public: // Sets default values for this actor's properties AMyShapeActor(); // Called when the game starts or when spawned virtual void BeginPlay() override; // Called every frame virtual void Tick( float DeltaSeconds ) override; UFUNCTION(BlueprintCallable, Category = "Size") void setLength(int new_length); UFUNCTION(BlueprintCallable, Category = "Size") void setWidth(int new_width); UFUNCTION(BlueprintCallable, Category = "Size") void setHeight(int new_height); virtual int getVolume() PURE_VIRTUAL(AMyShapeActor::getVolume, return 0;); static FORCEINLINE void Print(FString text_to_print, bool print_to_log = false, FColor color = FColor::Green, float TimeToDisplay = 1.5f) { if (GEngine) { GEngine->AddOnScreenDebugMessage(-1, TimeToDisplay, color, text_to_print); } if (print_to_log) { UE_LOG(LogTemp, Warning, TEXT("%s"), *text_to_print); } } };
#include "MyShapeActor.h" // Sets default values AMyShapeActor::AMyShapeActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; } // Called when the game starts or when spawned void AMyShapeActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AMyShapeActor::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } void AMyShapeActor::setLength(int new_length) { length = new_length; } void AMyShapeActor::setWidth(int new_width) { width = new_width; } void AMyShapeActor::setHeight(int new_height) { height = new_height; }
UCLASS() class VRDEMO_API AMyCubeActor : public AMyShapeActor { GENERATED_BODY() private: UStaticMeshComponent* my_static_mesh; protected: public: AMyCubeActor(); int getVolume(); };
#include "MyCubeActor.h" AMyCubeActor::AMyCubeActor() { PrimaryActorTick.bCanEverTick = true; static ConstructorHelpers::FObjectFinder<UStaticMesh> static_mesh(TEXT("StaticMesh'/Game/Geometry/Meshes/1M_Cube.1M_Cube'")); my_static_mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("static_mesh")); my_static_mesh->SetStaticMesh(static_mesh.Object); my_static_mesh->SetWorldScale3D(FVector(0.3f, 0.3f, 0.3f)); RootComponent = my_static_mesh; length = 11; width = 11; height = 11; // get reference to ourself as the Parent class pointer and call the proper getVolume function // this is dynamic linkage, where the function to call is determine at runtime AMyShapeActor* myself = &(*this); myself->getVolume(); } int AMyCubeActor::getVolume() { int volume = length * width * height; FString msg("Hi from Cube Actor Child - C++\nlength = "); msg.AppendInt(length); msg.Append(" , width = "); msg.AppendInt(width); msg.Append(", height = "); msg.AppendInt(height); msg.Append("\nVolume = "); msg.AppendInt(volume); Print(msg, false, FColor::Green, 10.0f); return volume; }
This example demonstrates:
- UE4 C++ does not support abstract classes and virtual functions like regular C++
- UE4 macros are necessary to create a pseudo abstract class and pure virtual function
- Need to use the macros: uclass (abstract) and PURE_VIRTUAL
Note: Pure virtual functions are technically not possible since UE expects every UE class to be instantiable (non-abstract)
If you found this post helpful, share it with others so they can benefit too.
Were the examples clear? Are there something else unique to UE4 and polymorphism that was not mentioned in this post?
Leave a comment or send me an email at steven@brightdevelopers.com. To stay in touch, follow me on Twitter.