Refresher on the use of Interfaces in C++

Hello,

So I am revisiting the usage of interfaces in C++ code - this has been a topic of contention for me for some time because it seems easy to make mistakes based on what I have read. Here are some several points I need clarification on with recent versions of the engine (4.22 and above).

Let’s start with this interface declaration:

class UMyInterface : public UInterface { GENERATED_BODY() }

class IMyInterface 
{
    virtual void Foo();
}

// Object implementing interface
class UMyObject : public UObject, public IMyInterface { ... }

Now, can my code call functions via the interface pointer directly? Say I pass it around to a function:

void SomeFunction( IMyInterface* interface )
{
    interface->Foo();
}

My understanding is - that interface pointer was casted from a concrete object at some point, so there can never be a case where the above code is illegal.

There is also the issue with using Execute_Foo() instead of the direct call. Can someone confirm that this is necessary if and ONLY if your interface is not implemented by a native class? So that means it works fine if I have a blueprint derived from a native class implementing the interface.

Another issue is the use of TScriptInterface - is it legal to initialize a TScriptInterface from MyInterface* directly? Some readings seem to indicate it is not - but I go back to my original point, why would it be illegal when the pointer ultimately references a concrete class? Can you not cast between IMyInterface* and UMyObject* ?

Personally, the only reason I need interfaces is for organizational matters in native code (i.e manager class not knowing about concrete implementations). It becomes troublesome because Abstract classes are out of the question when I want two different base classes to have common functionality. I am not concerned with implementing interfaces in BP at the moment, but any warnings with that are welcome.

1 Like

Hi!

First of all we should quickly clarify what it means for C++ code to be “legal”: if your code adheres to the rules of the C++ standard (at compile & runtime) it is considered well-formed or “legal”. That does not necessarily mean that it does what you want or that you are using the API correctly. For example, your class definitions are not legal because they are syntactically incorrect (they are missing a semicolon after the closing curly brace). On to your questions:

It would be very easy to construct a case where the above code would be “illegal”, e.g. passing a nullptr to the function.

Execute_Foo() will only be generated if the function is marked as Blueprint event so your code won’t even compile if it is not (because the function won’t exist). It is generated for Blueprint events because, well, the function may be implemented in Blueprint and simply calling the C++ function directly would bypass any Blueprint stuff. Again, it would be legal to do that but it’s certainly not what we want. In development builds you will run into a check macro if you try to call a Blueprint event of an interface directly so you can’t really make a mistake here.

I don’t know any reason why it wouldn’t be. I have not used the class myself but from looking at the source code it is essentially just a wrapper that adds some references and utility to the object. E.g. in your example you could just define TScriptInterface<IMyInterface> MyScriptInterface = MyRegularInterface; and call MyScriptInterface->Foo() as if it was the original interface (thanks to the overloaded -> operator). Whether that is the intended use I don’t know but it is legal to do it like that :wink:

Thanks for the response @GRIMTEC ! I use legal loosely to imply the behaviour will be what we expect (no crashes, null due to casts, and expected function overrides being called).

So assuming the interface pointer isn’t null, you can just seamlessly cast between an interface pointer and the concrete UObject pointer, right? Common C++ sense tells me that’s fair game.

As for Execute_Foo, is your statement applicable to both BlueprintImplementableEvent and BlueprintNativeEvent? If Foo was a BlueprintNativeEvent, and implemented by some class defined in C++, will calling MyInterface->Foo() work - i.e it would execute the C++ implementation OR the BP implementation if overriden.

As for the last point, it helps to emphasize why I needed TScriptInterface. In my native code, I will be passing interface pointers around in the same manner you pass around a base class. But at some point, there will be a class that need to store a variable of type MyInterface* - effectively owning the Object. But in order to keep UObjects from being GC’ed, you need the UPROPERTY() markup, and that doesn’t work with an interface pointer. That’s where TScriptInterface comes in. What I am getting from this answer is that I am free to pass around the raw MyInterface* around like typical C++ code, but wherever I need to claim ownership of the underlying object, I will need to use TScriptInterface in conjunction with UPROPERTY.

1 Like

Yes, if the object inherits from the interface you can do that, like with any other parent-child relationship. Interfaces are just classes after all.

Yes because both can be overwritten in Blueprint. But as I said previously if you do something wrong here you will notice it immediately, either because the Execute_ function doesn’t exist or because you call the regular function when you shouldn’t and run into a check macro that tells you not to do that.

Ok that’s right you can mark TScriptInterface<> as UPROPERTY, makes sense now.