Base struct array, pointer with casting

Hi guys,
Please help me with this, Iam completely stuck, thats blow my brain several days…
Everything is simple. I have different types of structs. For example:

USTRUCT()
struct FItem {
	GENERATED_BODY()
};

USTRUCT()
struct FNumber : public FItem {
	GENERATED_BODY()

	UPROPERTY()
	float Value;

	FNumber() {
		Value = 0.0f;
	}
};

Then I create an array of base type as usual and add some:

TArray<FItem> bucket;

FNumber n1;
n1.Value = 3.14f;
bucket.Add(n1);

Without casting all is ok (just for test):

FNumber* nPtr = (FNumber*)&n1;	// nPtr->Value = 3.14

but when I try to get pointer with casting - Value became very strange like its garbage collected or so:

// C style casting doesnt work
FNumber* nPtr = (FNumber*)&bucket[0]; // nPtr->Value is -431602080.000000 and changing every start

Also static_cast does not work. I tried to use const, default values, but I can sware that one time I saw this cating to FNumber was ok! I even try 4.13 version, VS2017 compiler and nothing! Maybe some mix of something gave me proper result those day :slight_smile:

So the question is how I can store different structs inside each other (nested structs, this array will be as another type of FItem) and cast them to proper type to operate with.

Thanks!

how about FNumber* nPtr = (FNumber*)&(bucket[0]) , just a thought.

Hi!

There is no troubles with GC in your sample. Your code does exactly following:

 TArray<FItem> bucket; // Creates an array of FItem. FItem have no members inside and have ~zero size (actually non-zero, but without your data)
 
 FNumber n1; // Works as desired
 n1.Value = 3.14f;
 bucket.Add(n1);  // Converts FNumber to FItem and then pass it to an Add method (removes your data completely). 

 FNumber* nPtr = (FNumber*)&bucket[0]; // Uoy assume that there is FNumber in array, but there is only FItem here, so it contains no data.

You can try the common practice for UE:

  1. TArray > YourArray // Creates an array of pointers to your further data
  2. YourArray.Add( MakeShareable( new YourBaseTypeSubclass ) ); // Creates an item and push it into array
  3. TSharedPtr YourItem = StaticCastSharedPtr< YourBaseTypeSubclass >( YourArray[0] ); // Cast type to an actual item
  4. Use YourItem as pointer

Also you can use UObject RTTI facility which gives type safety

TArray ObjectArray;
ObjectArray.Add( NewObject(this)); // this is a container for a newly created object. It is possible to create a new object inside any other UObject instance, but it will be better to create they inside your components or inside a transient package.
UChildObject* ptr = Cast(ObjectArray[0]);

!Important!
To prevent UObject array from being GC’ed you have to turn this array to UPROPERTY()

Wow, nothing to say, thats amazing!

TArray<TSharedPtr<FItem>> list;
list.Add(MakeShareable(new FNumber()));
TSharedPtr<FNumber> test = StaticCastSharedPtr<FNumber>(list[0]);
UE_LOG(LogTemp, Warning, TEXT("cast %f"), test->Value); // correct value

but how this differ from using common pointer array?
Can I still able to use tons of nested structs (of course with link to some UObjects)?
Thank you so much! Now I can sleep well 8-P

1 Like

When you create simple TArray list of some class UE have to allocate some memory to store your class data. It uses size of the class you give in the <> brackets. You can use simple lists if you reserve some memory in your base class to hold a biggest class you want to put into array. In c++ there is structure called ‘union’ designed to manage this case. It looks like this:
union Bag {
int MyInt;
float MyFloat;
FText MyText;
}
and you able to use either MyInt or MyFloat or MyText because they are overlapped in memory. If you will be careful it may work, but I never tested this structuretype with UE classes.

While you use TSharedPtr you can nest your structures as you did before - c++ just converts nested structures to a bigger one.

Haha I just miss the Pointer in my reply. Of course I meant difference between your shared pointers variant and common array of pointers. Thanks a lot, will try to test nested variant. Have a great day!

There is no difference between TSharedPtr and regular pointers, except one. Without TSharedPtr you have to track removing pointers from an array and call delete for removing pointers. Shared pointers tracks it automatically and your application will not crash if you copy a pointer from an array somewhere and then erase array.

To prevent this array of shared pointers from GC I have to put it under UPROPERTY, but it seems to impossible, right?

If you don’t use UObject’s inside you shouldn’t worry about GC. If you need to store UObjects safe inside your FClass I think there is only one opportunity to do this: make a helper UObject containing your TArray with UObject pointers (they should be just UObject* pointers, not TSharedPtr) and add this helper to a root set while spawning your FClass and remove from root set when FClass destroyed. To save your FStruct hierarchy you can just reference each uobject you have from that helper and existance of the helper will prevent your objects from being GC’ed. If it is possible to place TArray of objects inside some other existing UObject it would be better to use UObject pointers and their Cast(object) template.

(Update) I remembered that in UE already existing facility to save UObjects inside custom classes from GC. You just have to inherit your FClass container from FGCObject and implement AddReferencedObjects( FReferenceCollector& Collector ) which should add to it’s Collector all UObjects you want to save.

If you need to distinguish types of objects in your array it will be less painful to use UObjects instead. If it’s impossible you can use some tag in your base class for array items and check it while iterating.

Maybe Cast(object) will work with UStruct too, I never checked this.

Thanks a lot, just reading about UObjects inside such thing.
Only last question under this hood so when somebody find this topic they (and me:) could get whole picture… How can I iterate over array of shared pointers of base type to compare exact types of structs? If its FNumber do this, if FSomething do that? I cant find any reference to it…

In case if you miss my reply above about iteration, take a look once more please :slight_smile:

Update
It can be lame solution, but if I create something like:

USTRUCT()
struct FItem {
	GENERATED_BODY()

	UScriptStruct* owner;
};

then all types from this base type can write there their static class

owner = FNumber::StaticStruct();

and comparison works well:

FItem* iter = list.Items[i].Get();
if (iter->owner == FNumber::StaticStruct())
	UE_LOG(LogTemp, Warning, TEXT("its a FNumber"));