Unreal Engine 4 C++ Polymorphism Overview


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:

Cat and Dog are children class of Animal. Each has their own implementation for speak.

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

Visual Studio warning about static linkage occurrence

  • UE4 has a safe guard against static linkage. Static linkages are considered as errors, which causes compilation failure

Compilation errors in UE4 console

 

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

Dynamic linkage of getVolume function call output

  • Without redefining virtual function, the parent’s function runs when the child calls the function

When getVolume function is not redefined in child class, the parent version gets invoked

 

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.


About Steven To

Steven To is a software developer that specializes in mobile development with a background in computer engineering. Beyond his passion for software development, he also has an interest in Virtual Reality, Augmented Reality, Artificial Intelligence, Personal Development, and Personal Finance. If he is not writing software, then he is out learning something new.