Clarificatino on Unreal memory management for structs

Hi, I got a background in C++ and I am finding it difficult to separate what is memory managed and what is not, and how to use UObject or Struct correctly to integrate with blueprints.

My ‘‘game’’ is mostly a puzzle/question-solving game. The puzzles/questions are generated by a separate class I created as a UBlueprintFunctionLibrary.

This class has a GenerateQuestions(...) function returns a TMap<ELevelName, F_CPP_QuestionArray>. F_CPP_QuestionArray is a TArray<F_CPP_Question>. An AI controller calls GenerateQuestions() and stores them. It then pops a question of the right level and instructs a widget creator to make the UI for the F_CPP_Question. The AI binds the right events and continues till the right number of questions have been answered.

Below are the structs and the skeleton for the GenerateQuestions function:

USTRUCT(BlueprintType)
struct F_CPP_Question
{
    GENERATED_BODY()

    FORCEINLINE F_CPP_Question();
    //constructor
    explicit FORCEINLINE F_CPP_Question(
        const FString &pQuestion,
        const TArray<FString> &pAnswers,
        const FString &pCorrectAnswerString,
        const int32 pCorrectAnswerInt,
        const TArray<FString> &pQTags,
        const E_CPP_LevelName pQLevel);
   

    //~ The following member variable will be accessible by Blueprint Graphs:
    // This is the tooltip for our test variable.
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CIS|Exercise")
    FString Question;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CIS|Exercise")
    TArray<FString> Answers;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CIS|Exercise")
    FString CorrectAnswerString;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CIS|Exercise")
    int32 CorrectAnswerInt;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CIS|Exercise")
    TArray<FString> QTags;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CIS|Exercise")
    E_CPP_LevelName QLevel;
};

STRUCT(BlueprintType)
struct F_CPP_QuestionArray
{
    GENERATED_BODY()

    FORCEINLINE F_CPP_QuestionArray();

    explicit FORCEINLINE F_CPP_QuestionArray(const TArray<F_CPP_Question>& pQArray);

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CIS|Exercise")
    TArray<F_CPP_Question> QArray;
};


void U_CPP_ExerciseGenerator::GenerateExerciseQuestions(int32 MaxValEx, TArray<int32> ExerciseValuesToUse, int32 NumberOfQuestions, TMap<E_CPP_LevelName,F_CPP_QuestionArray>& QuestionsArrayByLevel){

F_CPP_QuestionArray QuestionArr = F_CPP_QuestionArray();

QuestionArr.QArray.Add(F_CPP_Question()); //logic missing on q gen

//will this not go out of scope?
QuestionsArrayByLevel.Add(E_CPP_LevelName::Level_1, QuestionArr);
}

So, my question is. Will the above work? Is &QuestionsArrayByLevel already managed by Unreal? I am confused as I read that UObjects are memory-managed and Structs are not. In that case, should I wrap F_CPP_QuestionArray in a UObject? Or, a smart_ptr?

In normal C++ I would normally just call new() but blueprints don’t support pointers if I am not wrong.

So, can someone let me know what is correct?
Thanks a lot.

There is no manual allocations, so Unreal has nothing to manage.

Apologies, I am not following.
Since the outputs are references I am starting to think that they are initialised by the Engine. So, those ones would be managed by Unreal.

My question is starting to become if I insert objects created on the stack to the output references then will they be copied to the heap? Or, will they become dangling ones?

Thanks,

UObjects are managed as in, if you allocate one using NewObject, it will be automatically garbage collected whenever nothing references it anymore.

That’s not the case for structs, there’s isn’t even a NewObject/NewStruct equivalent for them. Manually allocating a struct would generally be done using FMemory::Malloc, with the memory size retrieved from struct reflection data (if UStruct), or more traditional c++ methods for a traditional c++ structs. Only in those cases you need to do memory management or use helpers like TSharedPtr/TSharedRef.

When blueprints are involved you don’t need any of this, and even if you wanted you cannot because blueprints don’t support pointer types nor TSharedPtr/TSharedRef types. C++ exposed functions for blueprints always either return structs by value (copy) or by ref parameter, which are allocated and managed by the reflection system.

I think I get it now.
The references passed as inputs, say a TArray<> is allocated by the reflection system so anything I allocate inside the C++ function will then be automatically copied (I guess moved?) to the TArray<> dependencies.

I presume the move semantics make it efficient? I mean, otherwise allocating data in C++ functions that interact with the reflection system would cause massive data copies if they need to be copied again.

Is this correct? Apologies for all the questions!