Creating Objects C++

In a new C++ project I created two classes.
Cube.cpp

ACube::ACube()
{
	PrimaryActorTick.bCanEverTick = true;

	static ConstructorHelpers::FObjectFinder<UStaticMesh>C(TEXT("StaticMesh'/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube'"));
	Cube = CreateDefaultSubobject<UStaticMeshComponent>("Cube");
	Cube->SetupAttachment(RootComponent);
	Cube->SetStaticMesh(C.Object);
}

Spawner.cpp

ACube* C[20];

{
	Super::BeginPlay();
	
	for (int i = 0; i < 3; i++)
	{
		C[i] = GetWorld()->SpawnActor<ACube>(FVector(500.f, i * 200.f - 200, 100.f), FRotator(0.f));
	}
}

When starting, a warning appears:

LogActor: Warning: Cube /Game/StarterContent/Maps/UEDPIE_0_Minimal_Default.Minimal_Default:PersistentLevel.Cube_2 has natively added scene component(s), but none of them were set as the actor’s RootComponent - picking one arbitrarily

What does this mean and can it be fixed?

Actor’s USceneComponents (MeshComponent is a child of it) are structured as a tree. At this moment of construction your object have no RootComponent(so you literally doing Cube->SetupAttachment(nullptr) ), so you are asked to explicitely set root of this tree, which you can do via SetRootComponent(), so your cube’s constructor should looks like:

ACube::ACube()
{
	PrimaryActorTick.bCanEverTick = true;

	static ConstructorHelpers::FObjectFinder<UStaticMesh>C(TEXT("StaticMesh'/Game/StarterContent/Shapes/Shape_Cube.Shape_Cube'"));
	Cube = CreateDefaultSubobject<UStaticMeshComponent>("Cube");
	// Cube->SetupAttachment(RootComponent); // <-- removed, as it has no sence before the line added below
	Cube->SetStaticMesh(C.Object);
	SetRootComponent(Cube); // <-- added
}

Also note that ACube* C[20]; if it’s a global variable - is unsafe way of storing pointers in general case in context of UE, as the UObjects(including AActors) that have zero UPROPERTY() pointers pointing to it will be a garbage collected next time(iirc usually each 60seconds). Usually you store them in other objects like:

UCLASS()
class ACube: public AActor
{
    GENERATED_BODY();

    UPROPERTY() // <-- that's the key difference
    ACube* C[20];
};
1 Like

Thanks, it works.

But it’s very convenient. If I have 100 cubes and I create 101, then I will simply write:

C[100] = GetWorld()->SpawnActor<ACube>(FVector(500.f, 100 * 200.f - 200, 100.f), FRotator(0.f));

In this case, each cube will store pointers to other cubes. And if I want to create the 101st cube, then how should I write it? I don’t quite understand how this will work?

Instead of specifying the exact cube amount you can use a TArray in your header file

UPROPERTY() 
 TArray<ACube*> Cubes;

Then you can add / remove and empty the array dynamically.

1 Like

What does unsafe mean?
If I’m writing code

ACube* C[20];
void ASpawner::BeginPlay()
{
	Super::BeginPlay();
	
	C[0] = GetWorld()->SpawnActor<ACube>(FVector(500.f, 0.f * 200.f - 200, 100.f), FRotator(0.f));
	C[1] = GetWorld()->SpawnActor<ACube>(FVector(500.f, 1.f * 200.f - 200, 100.f), FRotator(0.f));
	C[1]->Destroy();
}

Array cell C[1] stores a pointer to a non-existent object. In this case, the garbage collector will not remove this pointer?

I’m not sure how exactly SpawnActor<> work, since AActors may be already safely tracked by UWorld* so in this particular case you maybe fine.

By unsafe i mean that your UObject may be destroyed ~() mid game by engine without your explicit command. Usually only UObjects that no longer have valid pointers to it will be destroyed, but improperly created UObjects may never had a valid pointers yet still being used. Note that it’s only applicable to UObjects(and all their childrens like AActors & etc).

You may read a bit about it here: Unreal Object Handling in Unreal Engine | Unreal Engine 5.3 Documentation

As for your example - GC won’t do anything to C[1] because:

  1. you storing raw pointers, not one of variety of smart pointers that engine provides;
  2. you array ACube* C[20]; is not visible to reflection system sinceit’s not marked as UPROPERTY(), so GC has even less reasons to care about it.

But what i was talking about, is that object pointed by C[0] may be deleted at the some arbitrary time later, while you can still think that your pointer is valid.

each cube will store pointers to other cubes

While it probably will work, it’s sounds wild. Usually you storing data you need in some manager that created your objects. For example, AGameMode in singleplayer game.

1 Like

Will this code be safe?

Cube.h

UCLASS()
class ACube: public AActor
{
    GENERATED_BODY();

    public:
	UPROPERTY()
	ACube* Cr;
};

Spawner.cpp

ACube* C[20];

{
	Super::BeginPlay();
	
	for (int i = 0; i < 3; i++)
	{
		C[i] = GetWorld()->SpawnActor<ACube>(FVector(500.f, i * 200.f - 200, 100.f), FRotator(0.f));
        C[i]->ACube::Cr = C[i];
	}
}

I think IrSoil made a mistake in first answer, you should put your array of cubes in the ASpawner class, not in the ACube class.

UCLASS()
class ASpawner : public AActor
{
    GENERATED_BODY();

    UPROPERTY()
    TArray<ACube*> C;
};

Using a global variable to store actors is not a good idea because if one of the actors is destroyed, either by calling Destroy() or by changing level, then you have absolutely no way to safely tell when an element is no longer valid in your array. They don’t automatically become nullptr when destroyed, and checking validity either by converting to bool or using IsValid is not safe. Of course if you are very meticulous you could theoretically keep your array up to date by making sure to null its elements in every possible scenario of actor destruction, but it’s extra pain for no reason.

A better way to store actors in static/global vars is by using FWeakObjectPtr or TWeakObjectPtr which is built with capabilities to validate object pointer outside of reflection system.

You almost certainly don’t need to do that though. In probably almost every case, it’s better to store your refs in UPROPERTY variable(s) even if that means creating an additional manager/singleton actor just to store them. In your case, you already have one : the spawner.

1 Like