Runtime Reflection in C++

Inspecting and manipulating types at runtime — from RTTI to custom systems

Iteration

I developed two RTTI systems. The first used a templatized class to store reflected values, which meant every instance of a class containing reflected variables also carried redundant metadata - type names, offset information, and validation flags - duplicated across hundreds or thousands of objects. That scrap data should have been stored in a central type registry, not per-instance.

The newer system fixes this by moving to compile-time reflection. All metadata is generated once per type during static initialization and shared across all instances. The per-object footprint is now just the raw data - no overhead, no duplication.

Difference

The old system:

class ObstacleSpawner : public Eclipse::Component
{
	COMPONENT_BASE_2(ObstacleSpawner, 3)

public:
	void Start() override;
	void Update() override;

private:
	SERIALIZED_FIELD(Eclipse::Assets::Prefab, ObstaclePrefab);
	SERIALIZED_FIELD(float, ObstacleSpawnInterval, 2);
	float obstacleSpawnTimer = 0.f;
};

SERIALIZED_FIELD macro expanded:

Eclipse::Reflection::SerializedVariable ObstaclePrefab{ "ObstaclePrefab", this, true, 0.1f };

Everything reflected is stored locally in the variable, which means every instance of this component will cause duplication of data.


The new system:

CLS
class Player : public Eclipse::Component
{
public:
	void Move();

private:
	[[serializefield]]
	float moveSpeed = 2.4f;
};
END_CLS

Build time code generation (via lexer), static/shared reflection data per class.

Key Takeaway

The shift from per-instance macro metadata to build-time code generation proved to be dramatically more efficient. In the old system, each reflected variable carried a 100-200 byte SerializedVariable object meaning a component with 5 reflected fields added over 500 bytes of duplicated metadata per instance. For 10,000 objects, that's 5+ MB of wasted memory.

The new system eliminates this entirely. Reflection data is generated once per type and stored in static memory. Each instance now pays only for its actual data - no overhead, no duplication, just efficiency.