# ASIO Service Registration and Dependency Injection: A Complete Guide

> Master ASIO service registration and dependency injection with execution_context. Learn to use use_service, add_service, and has_service for modular asynchronous components.

- Repository: [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio)
- Tags: how-to-guide
- Published: 2026-07-11

---

**ASIO's** `execution_context` provides a built-in service registry that enables dependency injection through `use_service`, `add_service`, and `has_service` APIs, allowing modular components to be registered once and retrieved on-demand across asynchronous operations.

The **chriskohlhoff/asio** library implements a sophisticated service registration and dependency injection system centered around the `execution_context` class. This architecture allows developers to register custom services that can be shared across strands, timers, and user-defined components without explicit constructor wiring. Understanding ASIO service registration and dependency injection is essential for building extensible asynchronous applications that leverage the library's internal resource management patterns.

## Core Architecture Components

ASIO's dependency injection mechanism relies on three primary classes that work together to provide type-safe service management.

### execution_context and the Service Base Class

The `asio::execution_context` class defined in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp) serves as the foundation for all execution contexts including `io_context`. This class provides the **service** abstract base class that all services must extend:

```cpp
class my_service : public asio::execution_context::service {
public:
  using key_type = my_service;  // Unique identifier for the registry
  // ...
};

```

Every service must declare a `key_type` that the registry uses to identify the service type. The base class provides lifecycle hooks including `shutdown` and `notify_fork` for proper resource management during context destruction or process forking.

### service_registry and service_maker

The `asio::detail::service_registry` class in [`include/asio/detail/service_registry.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/detail/service_registry.hpp) maintains a type-safe map of service keys to instances. During context construction, an optional **service_maker** functor (defined in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp)) can pre-populate the registry with default services. The registry ensures that each service type exists as a singleton per execution context, creating services on-demand using the service maker pattern or default construction.

## Service Registration Flow

The lifecycle of service registration follows three distinct phases:

1. **Construction** – When an `execution_context` (or derived `io_context`) is constructed, it may receive a service maker that pre-creates a set of services. The registry is initially empty if no service maker is provided.

2. **On-Demand Creation** – The first call to `use_service<MyService>(ctx)` triggers the registry to look up the service's key. If the key is missing, the registry constructs the service via its default constructor and stores the instance.

3. **Dependency Injection** – User code obtains a reference to the service simply by calling `use_service`. The returned reference can be stored, passed to other objects, or used directly, achieving constructor-less dependency injection.

## Service Registration APIs

ASIO provides three primary templates for service interaction that form the dependency injection interface.

### use_service Template Function

The `asio::use_service<Service>(context)` function defined in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp) implements lazy initialization. When called, the function queries the internal `service_registry` for the service key, constructs the service using its constructor if not present, and returns a reference to the existing or newly created instance:

```cpp
asio::io_context ioc;
auto& service = asio::use_service<custom_logger>(ioc);

```

This pattern eliminates the need for factories or dependency injection containers, allowing components to request services directly from the execution context.

### add_service and has_service

For explicit service registration, `asio::add_service<Service>(context, service_ptr)` in [`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp) inserts a pre-constructed service into the registry. This function throws `asio::service_already_exists` if the service type is already registered:

```cpp
auto* logger = new logger_service(ioc);
asio::add_service(ioc, logger);

```

The `asio::has_service<Service>(context)` function provides a safe check to determine if a service exists before attempting retrieval or addition, preventing exceptions in conditional initialization logic.

## Practical Implementation Examples

### Creating a Custom Logger Service

The following implementation demonstrates a complete custom service based on the reference in [`src/examples/cpp11/services/logger_service.hpp`](https://github.com/chriskohlhoff/asio/blob/main/src/examples/cpp11/services/logger_service.hpp). This service inherits from `execution_context::service` and implements the required interface:

```cpp
// logger_service.hpp
#include <asio.hpp>
#include <fstream>
#include <thread>
#include <sstream>

struct logger_impl {
  explicit logger_impl(const std::string& id) : identifier(id) {}
  std::string identifier;
};

class logger_service : public asio::execution_context::service {
public:
  using key_type = logger_service;
  using impl_type = logger_impl*;

  explicit logger_service(asio::execution_context& ctx)
    : asio::execution_context::service(ctx),
      work_(asio::make_work_guard(work_io_context_)),
      work_thread_([this]{ work_io_context_.run(); }) {}

  impl_type null() const { return nullptr; }

  void create(impl_type& impl, const std::string& id) {
    impl = new logger_impl(id);
  }

  void destroy(impl_type& impl) {
    delete impl;
    impl = null();
  }

  void log(impl_type& impl, const std::string& msg) {
    std::ostringstream os;
    os << impl->identifier << ": " << msg;
    asio::post(work_io_context_, std::bind(&logger_service::log_impl, this, os.str()));
  }

private:
  void log_impl(const std::string& text) {
    ofstream_ << text << std::endl;
  }

  asio::io_context work_io_context_;
  asio::executor_work_guard<asio::io_context::executor_type> work_;
  std::thread work_thread_;
  std::ofstream ofstream_;
};

```

This service encapsulates its own `io_context` for asynchronous logging operations, demonstrating how services can own their own execution resources while being managed by the parent context.

### Accessing Services via use_service

Client code retrieves the logger service without explicit construction, achieving dependency injection:

```cpp
#include <asio.hpp>
#include "logger_service.hpp"

int main() {
  asio::io_context ioc;

  // Service is created automatically on first call
  logger_service& logger = asio::use_service<logger_service>(ioc);

  logger_service::impl_type log_impl;
  logger.create(log_impl, "application");

  logger.log(log_impl, "Service registration complete");

  logger.destroy(log_impl);
  return 0;
}

```

The `asio::use_service<logger_service>(ioc)` call transparently handles service instantiation, returning a reference that can be stored or passed to other components.

### Explicit Service Registration with add_service

For scenarios requiring custom initialization parameters, use `add_service`:

```cpp
int main() {
  asio::io_context ioc;

  // Manual construction with custom parameters
  logger_service* custom_logger = new logger_service(ioc);

  // Explicit registration
  asio::add_service(ioc, custom_logger);

  // Verify presence
  if (asio::has_service<logger_service>(ioc)) {
    logger_service& ref = asio::use_service<logger_service>(ioc);
    // ref points to *custom_logger
  }
}

```

This pattern is essential when services require constructor arguments that cannot be provided by the default service creation mechanism.

## Key Source Files in chriskohlhoff/asio

- **[`include/asio/execution_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/execution_context.hpp)**: Defines `execution_context`, the `service` base class, and the public API templates `use_service`, `add_service`, and `has_service`.
- **[`include/asio/detail/service_registry.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/detail/service_registry.hpp)**: Implements the internal type-safe registry that maps service keys to concrete instances.
- **[`src/examples/cpp11/services/logger_service.hpp`](https://github.com/chriskohlhoff/asio/blob/main/src/examples/cpp11/services/logger_service.hpp)**: Reference implementation demonstrating custom service patterns including asynchronous operation handling.
- **[`include/asio/io_context.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/io_context.hpp)**: Concrete execution context that inherits service registration capabilities.
- **[`include/asio/strand.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/strand.hpp)**: Shows how built-in services like `strand_executor_service` utilize the registration system.

## Summary

- **ASIO service registration** centers on `execution_context`, which owns a `service_registry` for managing service lifetimes and singleton instances per context.
- The `use_service` template provides lazy initialization, automatically creating services on first access and returning references that enable dependency injection.
- `add_service` enables explicit registration with pre-constructed objects, while `has_service` allows safe existence checks to avoid duplicate registration exceptions.
- Custom services must inherit from `execution_context::service` and declare a `key_type` for registry identification.
- This architecture underpins ASIO's built-in components including strands, timers, and thread pools, providing a consistent pattern for extensible asynchronous design.

## Frequently Asked Questions

### What is the purpose of the `key_type` declaration in ASIO services?

The `key_type` serves as a unique identifier for the service type within the registry. When you call `use_service<MyService>`, the registry uses `MyService::key_type` to look up the instance. This design allows the same service implementation to potentially operate with different keys, though typically `key_type` is typedef'd to the service class itself. The separation between the service class and its key enables flexible service composition while maintaining type safety.

### Can I register multiple instances of the same service type in one io_context?

No, the `service_registry` enforces a singleton pattern per service type within a single `execution_context`. If you attempt to call `add_service` with a service type that already exists, the library throws `asio::service_already_exists`. To manage multiple similar resources, you should extend your service implementation to handle multiple internal instances, or create distinct service types for each resource variant.

### What happens to services when the io_context is destroyed?

When an `execution_context` is destroyed, it automatically invokes `shutdown` on all registered services in reverse order of creation. This mechanism, implemented in `service_registry`, ensures that services release resources and stop their internal `io_context` or threads before the parent context completes destruction. Services must implement the `shutdown` virtual function to clean up any pending operations or background threads.

### Is the ASIO service registry thread-safe?

Service creation through `use_service` is thread-safe—the registry ensures that only one thread constructs the service even if multiple threads simultaneously request the same service type. However, once retrieved, the service object itself is not inherently thread-safe unless documented otherwise. Custom services must implement their own synchronization (such as strands or mutexes) for thread-safe operation, as the registry only guarantees safe initialization and singleton access.