C++ Struct is Having its Data Cleared in Blueprints

I have created a struct that is passed to blueprints via a UObject function. Using a debugger and UE_LOGs, I have confirmed the struct data exists as populated when the getter function is being called. However, in blueprints, the struct return value is being read as having the default values instead of the populated values. I have never had this issue before with structs, and for the life of me, I cannot figure out what’s going on.

Does anyone have any ideas about what might be the problem?

USTRUCT(BlueprintType)
struct FMyStruct
{
	GENERATED_BODY()

	/**
	 *
	 */
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	FString String1 = "Default Value";

	/**
	 *
	 */
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	FString String2 = "";

	/**
	 *
	 */
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	FString String3 = "";
};
UCLASS(BlueprintType, DefaultToInstanced, EditInlineNew)
class UMyObject : public UObject
{
	GENERATED_BODY()

public:
        // Attempt 1
        UFUNCTION(BlueprintCallable, BlueprintPure)
        const FMyStruct& GetStruct();

        // Attempt 2
        UFUNCTION(BlueprintCallable, BlueprintPure)
        MyStruct& GetStruct();

        // Attempt 3
        UFUNCTION(BlueprintCallable)
        MyStruct& GetStruct();

        // Attempt 4
        UFUNCTION(BlueprintCallable)
        void GetStruct(FMyStruct& MyStruct);
};

adding the override node

and when making the struct

Default values are present

MyObject.cpp (255 Bytes)
MyObject.h (914 Bytes)
MyActor2.cpp (814 Bytes)
MyActor2.h (626 Bytes)

Thank you for the reply @3dRaven. Unfortunately, what you’re showing does not address the issue. The value of the struct gets set in C++ and needs to be passed to Blueprints, but the return value of GetStruct() in Blueprints shows the default values, while the UE_LOG and breakpoints show the populated values in C++.

class UMyObject : public UObject
{
	GENERATED_BODY()

public:
        UFUNCTION(BlueprintCallable, BlueprintPure)
        const FMyStruct& GetStruct();

        // Not a UFUNCTION because it is only used in C++.
        void SetStruct(const FMyStruct&);

};

Setting values and passing in struct in c++

Retrieval in blueprints

print out in game
image

Note that you are passing in a const ref of the struct in your version.
const means it’s constant and cannot be changed from within the function itself which counteracts the pass by ref which would only be usable if you planned on changing the struct from with the function.

I think the perceived error lies in your planning and execution of your pass in function.

You are probably making internal changes that have no impact on the passed in variable, in essence changing nothing with the internals of the function.

What exactly are you doing in the cpp? Are you just passing the struct to an internal variable or trying to modify it in some way?

It should work if you are just passing the var to a private version. I get the correct values even with a setter like this:

void UMyObject::SetStruct(const FMyStruct &MyStruct)
{
	_MyStruct = MyStruct;
}
1 Like
USTRUCT(BlueprintType)
struct FMyStruct
{
    GENERATED_BODY()

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FString String1 = "Default Value";

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FString String2 = "";

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FString String3 = "";
};
UCLASS(BlueprintType, DefaultToInstanced, EditInlineNew)
class UMyObject : public UObject
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable, BlueprintPure)
    const FMyStruct& GetStruct()
    {
        // Debugger shows Struct as populated with "Value 1" "Value 2" and "Value 3"
        const MyStruct& Struct = LinkedList->GetCurrent()->GetStruct();

        // Debugger and TempLog show Struct as populated with "Value 1" "Value 2" and "Value 3"
        UE_LOG(/* Outputs Struct.String1, Struct.String2, and Struct.String3 */);

        return Struct; // Blueprint debugger shows "Default Value" "" and ""
    }

private:
    MyLinkedList* LinkedList = nullptr; // Value gets assigned in another function.
};
class MyLinkedList
{
public:
    void Add(FMyStruct& struct);

    Node* GetCurrent() { return Current; }

private:
    Node* Head;
    Node* Current;
};
class Node
{
public:
    Node(const TArray<FMyStruct>& InStructs) : Structs(InStructs) {}

    const MyStruct& GetStruct() const { return Structs[0]; }

private:
    TArray<FMyStruct> Structs;

    Node* Next = nullptr;
};

Node is constructed and added to the Linked List in another class.

By placing breakpoints in GetStruct and on the Blueprint (before, on [when testing without BlueprintPure], and after the GetStruct node), I have confirmed that the node that is returning the default values is the node that is calling the function when the breakpoints show the populated values.

I am aware that passing by const& prevents editing the values. That is the intended design. I’m passing by reference as a performance improvement, and I do not want the values modified.

The struct gets constructed with certain variables and is never, nor should ever be, modified after that. I was originally doing BlueprintReadOnly for the struct variables but changed to BlueprintReadWrite and EditAnywhere for testing.

UPROPERTY(BlueprintReadOnly)
FString String1 = "Default Value";

In Blueprints, I am passing the struct from the getter to a Widget Blueprint.

1 Like

Could it be that MyLinkedList is being garbage collected in the meantime?
It’s not marked as a uproperty so GC can gather it.

I’d have to implement the whole thing to test but that’s a weak point at first glance.

After 5 hours of tracing and debugging, I would LOVE for something that simple to be the fix. I didn’t think I could mark non-UObject custom classes UPROPERTY.

I so badly wanted that to be the solution. I refactored MyLinkedList and Node to be UObjects to make things compile after marking the UPROPERTIES, which required me doing things that makes my embedded C++ background scream at me. It still doesn’t work…

USTRUCT(BlueprintType)
struct FMyStruct
{
    GENERATED_BODY()

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FString String1 = "Default Value";

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FString String2 = "";

    UPROPERTY(BlueprintReadWrite, EditAnywhere)
    FString String3 = "";
};
UCLASS(BlueprintType, DefaultToInstanced, EditInlineNew)
class UMyObject : public UObject
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintCallable, BlueprintPure)
    const FMyStruct& GetStruct()
    {
        // Debugger shows Struct as populated with "Value 1" "Value 2" and "Value 3"
        const FMyStruct& Struct = LinkedList->GetCurrent()->GetStruct();

        // Debugger and TempLog show Struct as populated with "Value 1" "Value 2" and "Value 3"
        UE_LOG(/* Outputs Struct.String1, Struct.String2, and Struct.String3 */);

        return Struct; // Blueprint debugger shows "Default Value" "" and ""
    }

private:
    UPROPERTY()
    UMyLinkedList * LinkedList = NewObject<UMyLinkedList>();
};
UCLASS()
class UMyLinkedList : public UObject
{
    GENERATED_BODY()

public:
    UMyLinkedList() : Super(), Head(nullptr), Current(nullptr) {}

    void Add(FMyStruct& struct);

    Node* GetCurrent() { return Current; }

private:
    UPROPERTY()
    UNode* Head;

    UPROPERTY()
    UNode* Current;
};
UCLASS()
class UNode : public UObject
{
    GENERATED_BODY()

public:
    UNode() : Super() {}

    Add(const TArray<FMyStruct>& InStructs) { Structs = InStructs; }

    const MyStruct& GetStruct() const { return Structs[0]; }

private:
    UPROPERTY()
    TArray<FMyStruct> Structs;

    UPROPERTY()
    UNode* Next = nullptr;
};

your linked list has no logic regarding adding.

Does the add function in the MyLinked list supposed to create a new node then populate it with the passed in structs?

Does it insert as the first element of the linked list or is it injected in the current place?

I’m guessing all of the UNodes are linked to each other via a pointer Next, Though I don’t see any logic to traverse the Linked list.

Theoretical logic:

void UMyLinkedList::Add(const FMyStruct& InStructs)
{
	UNode* n = NewObject<UNode>(this);	
	n->Add(InStructs);

	if (Head == nullptr) {
		Head = n;
	}
	if (Current != nullptr) 
	{
		n->Next = Current->Next;
	Current->Next = n;
	}	
}

I’m not sure how far back unreal will honor the pointer chain. Only the current is probably a UPROPERTY the rest are dangling onto the current.

Edit:

So far I’ve narrowed down the error to you calling NewObject as a default initializer inside of a UPROPERTY. You can’t do that.

Narrowing down the errors…

Also calling

const FMyStruct& GetStruct() const { return Structs[0]; }

in case of the struct not having any positions will cause a nullptr exception.

Narrowed down the problems and caught all of the nullptrs along the way

def.zip (881.2 KB)

UE 5.4

Default values show up correctly in blueprints and can be overridden with a simple function for now just to prove the list is working correctly.

You can only use NewObject once the world is instantiated. You cannot init with it.

1 Like

After my last post, I tried circumventing the blueprint issue by running the struct to the widget through a C++ parent class instead. I came down with the flu shortly after.

Long story short, during my prototyping, the compiler forced me to overload the = operator on FMyStruct. Please don’t ask me why; that was ages ago in my brain. Anyways, it turns out that overloaded = was the issue.

FMyStruct& operator=(const FMyStruct& right)
{
	if (this == &right) return *this;

	FMyStruct* left = new FMyStruct(right.String1, right.String2, right.String3);
	return *left;
}

Breakpoints showed no issues with the overload, but the issue of data being reverted to default was showing in my C++ implementation.

FORCEINLINE void SetStructData(FMyStruct Struct) // Also used (const FMyStruct& Struct)
{
	MyStruct = Struct;

	UE_LOG(/*Outputs Struct*/); // Displays provided values
	UE_LOG(/*Outputs MyStruct*/); // Displays default values
}

So, I removed the overload to test, which fixed the issue.

Now, I only wish I had saved my earlier version since that was cleaner. Oh well, I’ll just have to clean it up later.

Now that the issue is fixed, we come to the real question: why did that = overload break it?

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.