Ctorium is a modern C++ dependency injection container focused on easy declarative discovery, explicit bean handles, lifecycle metadata, and application-level runtime composition. Its Inversion of Control (IoC) and Dependency Injection ( DI) runtime is powered by compile-time (AoT) reflection.
It lets you declare application services directly in C++ with attributes, discover them through visible reflection roots, and resolve them at runtime through a tracked bean context.
Ctorium is designed around a simple rule: the C++ annotated source is the source of truth. There is no external scan step and no compiled generator.
Inversion-of-Control (IoC) is the broader principle: application objects do not control how their dependencies are created, selected, wired, or managed. That responsibility is moved to the container.
Dependency-Injection (DI) is a concrete way to apply IoC: objects declare what they need, and those dependencies are supplied from the outside through constructors, factories, or other injection points.
Ahead-of-Time (AoT) means compile-time preparation. In Ctorium, dependency metadata and construction information can be produced before the program runs, instead of being discovered lazily during runtime resolution. The runtime still resolves and manages objects, but it consumes a model that was already prepared at compile time.
In short: IoC decides who controls construction, wiring, resolution, and lifecycle. DI defines how objects receive their dependencies. AoT defines when dependency metadata and construction structure are prepared: at compile time, before runtime resolution.
Ctorium provides all three. ctr::BeanContext controls resolution, runtime bindings, and lifecycle, while objects
and factories declare dependencies through ctr::Bean<T> parameters. AoT means Ctorium prepares dependency metadata
and construction paths at compile time, then uses that prepared information during runtime resolution.
C++ 26 with reflection capable compiler (gcc 16.1.0+ is fine).
Compile and run toolchain-check to check your toolchain against the C++ 26 reflexion API needed by Ctorium.
#include <string_view>
#include <ctr/Ctorium.hpp>
namespace app {
struct Logger {
virtual ~Logger() = default;
virtual void log(std::string_view message) = 0;
};
struct [[=ctr::singleton{}]] ConsoleLogger : Logger {
void log(std::string_view message) override;
};
struct [[=ctr::singleton{}]] GameService {
explicit GameService(ctr::Bean<Logger> logger)
: logger_(logger) {}
void start() {
logger_->log("game started");
}
ctr::Bean<Logger> logger_;
};
} // namespace app
int main() {
ctr::BeanContext& context = ctr::BeanContext::resolveContext("game");
context.discover<^^app>();
context.start();
ctr::Bean<app::GameService> game = context.resolve<app::GameService>();
game->start();
context.stop();
}Interesting stuff in this example :
- ConsoleLogger is declared as a singleton bean
- ConsoleLogger implements Logger
- GameService is declared as a singleton bean
- GameService declares a dependency on a Logger through its constructor
In our main :
- we create an IoC context named "game"
- we discover the
appnamespace as the reflection root. - we start the context
- we ask for a
GameService.
This is where the magic happens. The Ctorium's IoC context resolves the object graph for you:
- it knows
GameServiceneeds aLogger - it knows a
ConsoleLoggercan fit the request - it creates, if needed, a
ConsoleLogger - it creates, if needed, a
GameServicewith theConsoleLoggerinjected - it returns the ready-to-go
GameServiceto the caller
Then we start the GameService logic and finally properly stops the context to clean things up.
The main function stays clean, simple, and focused on application flow.
You focus on your business logic: object discovery, dependency resolution, wiring, lifecycle, and tracked handles are taken care of by Ctorium.
Boiler plate vanished and tedious wiring is now a simple declaration.
Want to find out more? Dive into our guide.
-
C++26 dependency injection powered by compile-time reflection
- no external scanner
- no separate generator executable
- no generated source files to commit or include
- toolchain check tool
- header-only library
-
dynamic link library
-
Easy attribute-based bean declaration model
-
singleton -
prototype -
threadLocal -
session -
named -
factory -
postConstruct -
preDestroy
-
-
Compile-time discovery
- discovery from visible headers, modules, and namespaces
- multiple reflection roots in one
discover<...>()call - per-context descriptor and annotation catalog
-
Injection models
- constructor injection through
ctr::Bean<T> - setter injection through
ctr::Bean<T> - named injection points
- automatic constructor selection
- deterministic error on ambiguous constructor candidates
- constructor injection through
-
Tracked lightweight bean handles
- typed handles with
ctr::Bean<T> - type-erased handles with
ctr::AnyBean - safe cast helpers with
exact<T>(),compatible<T>(),cast<T>()andtryCast<T>() -
bean.context()returns the owning context
- typed handles with
-
Lifetime models
- singleton
- eager and lazy singletons
- prototype
- thread-local singleton, one instance per context, key and thread
- session
-
Runtime bean contexts
- default context for most applications
- fully isolated parallel contexts for advanced use cases
- explicit
start()/stop()lifecycle -
ctr::BeanContextcan be injected like any other bean - scoped contexts with session lifetime (
resolveScope,start/stop/restart) - scope
userDataassociation - a bean can retrieve its owning context with
bean.context() - expose their lifecycle state
- destroyBean() api
-
Qualifier-based resolution
- named qualifiers
- priority-based selection
- runtime default named selection with
defaultNamed<T>(...)
-
Dependency resolution
- single bean resolution with
resolve<T>() - automatic dependency graph resolution
- deterministic error on missing, ambiguous, or cyclic dependencies
- single bean resolution with
-
Runtime instance binding
- adopt external singleton instances with
bindSingleton<T>() - adopt external session instances with
bindSession<T>() - context-owned bound instances
- named and prioritized runtime bindings
- bound instances participate in resolution, lifecycle, listeners, and shutdown
- adopt external singleton instances with
-
Bean lifecycle hooks
- initialization hooks with
postConstruct - destruction hooks with
preDestroy - hook dependency injection through
ctr::Bean<T>
- initialization hooks with
-
Beans lifecycle listener
- typed listener registration with
on<T>() - global listener registration with
on() - explicit listener removal with
remove(handle) -
onInitialized, beforepostConstruct -
onCreated, afterpostConstruct -
onPreDestroy, beforepreDestroy -
onDestroyed, afterpreDestroy - listener priorities
- snapshot-based listener dispatch
- metadata-aware listener callbacks
- typed listener registration with
-
Reflection metadata
- metadata for resolved beans and descriptors
- scanned annotations on types, methods, and parameters
- metadata views for typed and type-erased listeners
- factory method metadata for factory-produced beans
-
Factories
- factories are singleton beans with constructor-injected dependencies
- producer methods can create
singletonbeans - producer methods can create
prototypebeans - producer methods can create
sessionbeans - producer methods can create
threadLocalbeans - produced types do not need to be annotated
- producer methods can return
Torstd::unique_ptr<T> - produced beans participate in lifecycle hooks and listener phases
- produced beans participate in metadata
-
Deterministic and clear error model
- deterministic Ctorium errors for configuration, state, and resolution failures
- user exceptions are propagated unchanged
-
Thread-safety
- concurrent singleton resolution (double-checked locking)
- snapshot-based listener dispatch (lock-free reads)
- listener registration and removal API
- runtime default named selection
- runtime bindings