Multi-Level Inheritance Issue

Hello, there seems to be some issue in implementing interface methods in an abstract class.

Here is my interface:

class SHOOTER_API IInventorySlot
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:

	UFUNCTION(Category = "Inventory Slots")
	virtual void Drop() = 0;

	UFUNCTION(Category = "Inventory Slots")
	virtual bool IsEmpty() = 0;

Here is my abstract class:

UCLASS(Abstract)
class SHOOTER_API UInventorySlotBase : public UObject, public IInventorySlot
{
	GENERATED_BODY()
	
public:
	UInventorySlotBase(const FObjectInitializer& ObjectInitializer);

	UFUNCTION(Category = "Inventory Slots")
	virtual void Drop();

	UFUNCTION(Category = "Inventory Slots")
	virtual bool IsEmpty();

Each function in the abstract base class above is implemented in the .cpp file with appropriate local logic, including IsEmpty(), which is going to throw as pure-virtual below.

Here is my implementing class:

UCLASS()
class SHOOTER_API UPrimarySlot : public UInventorySlotBase
{
	GENERATED_BODY()

public:
	UPrimarySlot(const FObjectInitializer& ObjectInitializer);

Finally, here is the class where these are used:

UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class SHOOTER_API UInventoryComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	// Sets default values for this component's properties
	UInventoryComponent();

protected:
	// Called when the game starts
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
	virtual void AddSlottableToSlots(TScriptInterface<ISlottable> slottableToAdd);

protected:
	virtual int GetIndexForSlotType(SlotType slotType);
	virtual void InitInventorySlots();

	TArray<TScriptInterface<IInventorySlot>> InventorySlots;

	TScriptInterface<IInventorySlot> MeleeSlot;
	TScriptInterface<IInventorySlot> PrimarySlot;
	TScriptInterface<IInventorySlot> SecondarySlot;

and its cpp:

void UInventoryComponent::InitInventorySlots()
{
	PrimarySlot = CreateDefaultSubobject<UPrimarySlot>(TEXT("PrimarySlot"));
	SecondarySlot = CreateDefaultSubobject<USecondarySlot>(TEXT("SecondarySlot"));

	InventorySlots.Add(PrimarySlot);
	InventorySlots.Add(SecondarySlot);
}

void UInventoryComponent::AddSlottableToSlots(TScriptInterface<ISlottable> slottableToAdd)
{
	if (slottableToAdd)
	{
		SlotType type = slottableToAdd->GetSlotType();
		int index = GetIndexForSlotType(type);
		TScriptInterface<IInventorySlot> slot = InventorySlots[index];

		if (!(slot->IsEmpty()))
		{
			slot->Drop();
		}

		slot->Insert(slottableToAdd);
	}
}

That last class is an actor component. The blueprint for said actor calls on construction a code path that eventually hits “AddSlottableToSlots”, and when hitting “compile”, I get this error:

Fatal error: 
[File:D:/build/++UE5/Sync/Engine/Source/Runtime/Core/Private/Windows/WindowsPlatformMisc.cpp] [Line: 476] Pure virtual function being called

UnrealEditor_Core!PureCallHandler() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Private\Windows\WindowsPlatformMisc.cpp:478]
VCRUNTIME140
UnrealEditor_Shooter!UInventoryComponent::AddSlottableToSlots() [C:\pathToMyProject\Source\Shooter\Inventory\InventoryComponent.cpp:102]

where line 102 is

if (!(slot->IsEmpty()))

It seems like the base class implementation of IsEmpty() is not being found. I have tried every combo of using “override” and marking as “UFUNCTION()” or not.

I would like this pattern:
Interface (defines, well, interface of callable functions) → abstract base class (gives definitions that can be overridden if necessary) → implementation classes. Is this pattern not tenable in unreal engine?

I am professionally used to enterprise C#, so I am well-versed in OOP and interfaces and have strong opinions about code cleanliness and organization. What I am less sure is how a multi-level inheritance would or would not work in UE C++. Any help or similarly elegant alternate patterns would be appreciated.

I am not sure how interfaces/UInterface calls its functions under the hood, but what you could try is giving the functions of IInventorySlot a default body, maybe that’ll help.

class SHOOTER_API IInventorySlot
{
	GENERATED_BODY()

	// Add interface functions to this class. This is the class that will be inherited to implement this interface.
public:

	UFUNCTION(Category = "Inventory Slots")
	virtual void Drop() {}

	UFUNCTION(Category = "Inventory Slots")
	virtual bool IsEmpty() { return true; }

For implementing interfaces in a child you always want to use override because it’s literally inheriting the function, so make sure that IsEmpty has override in UInventorySlotBase.

I imagine what’s happening here is that your code has no clue if InventorySlots[index] has an implementation or not so they assume it’s pure virtual. Since you’re using TScriptInterface<IInventorySlot> which the compiler doesn’t know has an implementation in child classes, a default body might help like the poster above says.

If that doesn’t work, try using objects instead of TScriptInterface, so have an array of UInventorySlotBase instead of TScriptInterface

BUT

I’m not sure why you’re using an interface at all. Interfaces are most useful when you need a single function across a wide variety of classes. In my project, I use a DamageTakingInterface for every class that needs to take damage from anything. So enemies, destructible environments, projectiles and all sorts of things that have nothing in common share this functionality because of the interface.

But it seems like all your inventory slots inherit from UInventorySlotBase, so why use an interface here at all? Just have your functions in the abstract base class and store your array as an array of UInventorySlotBase. I understand you’d like to use the pattern but I don’t see any advantages of having the additional interface layer over just having everything on the base class.

Hello, thanks for your replies. Some debug logging ended up showing that the pattern usually works; however, for some reason, clicking “compile” on the character blueprint would call the constructor of the component 5+ times, and it would fail sometime then. Rather than figure out why it fails the nth time, I’ve moved the call to BeginPlay since nothing else will be accessing the TArray before play. For good measure, I’ve made my abstract class implementation functions non-virtual, and each one now points to an “impl” function that is virtual should children want to override.

Then both responders essentially said, “just put default implementations in your interface (or in your base class, and ignore the interface)”. There are many organizational reasons to have interface definitions (the abstract idea, not necessarily unreal engine’s implentation) separate from any sort of implementation. The kernel at the root of these reasons is that consumers of the interface only need to know what it can get from an interface, so that should be all I need to define in my interface. Then some reasons that stem from that:

  • I can make as many implementations of the interface as I like, which is especially useful for testing
  • I never need to think about “which level” (i.e. impl class or interface’s) version of the function is being called
  • An implementation-less interface is a clear statement of intent about how other code can call it
  • One engineer can make an interface, and leave it to someone else (or themselves later) to implement - “programming to the interface”

This is a foundational idea of modern OOP programing. You can read more about this here: Interface (computing) - Wikipedia

You’d be correct that I could kind of fake the above with abstract classes and virtual implementation functions that aren’t implemented. I wanted to first try to get the built-in thing working, and I think I have, so hopefully I can just continue to do that.

1 Like

Glad you were able to get it working!

That makes a lot of sense. Thanks for taking the time to elaborate on it.