# How to Implement a Custom ASIO I/O Object: A Complete Guide

> Learn to implement a custom ASIO I/O object by defining a service and wrapper class. This guide provides the steps for low-level implementation and high-level operations.

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

---

**To implement a custom ASIO I/O object, you must define a service class containing the low-level implementation, then derive a public wrapper class from `asio::basic_io_object<YourService>` that exposes high-level operations.**

ASIO's extensibility relies on the **service / I/O object** pattern, which allows you to integrate custom asynchronous resources into the ASIO ecosystem. According to the [chriskohlhoff/asio](https://github.com/chriskohlhoff/asio) source code, this architecture separates the user-facing interface from the platform-specific implementation, enabling seamless integration with `io_context`, executors, and completion tokens.

## Understanding the Service/Object Architecture

ASIO implements all I/O objects using a two-layer design. The **service** manages the actual native resources (file descriptors, sockets, timers), while the **I/O object** provides the user API and lifetime management.

In [`include/asio/basic_io_object.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_io_object.hpp), the `asio::basic_io_object` template class handles the generic plumbing: storing a reference to the service, forwarding `get_executor()`, and managing the implementation's lifetime. Your custom object inherits from this base class, which automatically handles construction, destruction, and move semantics through the service interface.

Reference implementations like `basic_socket` in [`include/asio/basic_socket.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_socket.hpp) demonstrate this pattern in production code, showing how built-in objects delegate operations to their associated services.

## Defining the Service Class

The service is the core of your custom I/O object. It must define how native resources are created, destroyed, and moved between object instances.

### Required Service Members

Every service must contain these specific members:

- **`using implementation_type = ...;`** – The concrete type holding native resources (e.g., file descriptors).
- **`void construct(implementation_type&)`** – Initializes the implementation (opens descriptors, allocates buffers).
- **`void destroy(implementation_type&)`** – Cleans up resources (closes descriptors, frees memory).
- **`asio::io_context& get_io_context()`** – Returns the associated `io_context` (typically stored as a member).

### Optional Move Semantics

For moveable I/O objects, implement these additional members:

- **`void move_construct(implementation_type& dest, implementation_type& src)`** – Transfers ownership during move-construction.
- **`void move_assign(implementation_type& dest, service_type&, implementation_type& src)`** – Transfers ownership during move-assignment.

Here is a minimal service implementation:

```cpp
#pragma once
#include <asio.hpp>
#include <atomic>
#include <cstdio>

namespace my_namespace {

struct my_custom_service
{
  // The concrete implementation that holds a native resource.
  struct implementation_type
  {
    int native_handle = -1;          // Example: a POSIX file descriptor.
  };

  // Called by `basic_io_object` ctor.
  void construct(implementation_type& impl)
  {
    impl.native_handle = ::open("/dev/null", O_WRONLY);
    std::printf("my_custom_service::construct – fd=%d\n", impl.native_handle);
  }

  // Called by `basic_io_object` dtor.
  void destroy(implementation_type& impl)
  {
    if (impl.native_handle != -1)
    {
      ::close(impl.native_handle);
      std::printf("my_custom_service::destroy – fd closed\n");
    }
  }

  // Optional – enable move‑construction of the I/O object.
  static constexpr bool is_movable = true;

  void move_construct(implementation_type& dest,
                      implementation_type& src) noexcept
  {
    dest.native_handle = src.native_handle;
    src.native_handle = -1;
  }

  void move_assign(implementation_type& dest,
                   my_custom_service&,
                   implementation_type& src) noexcept
  {
    // Simple implementation: destroy old, then move‑construct.
    destroy(dest);
    move_construct(dest, src);
  }

  // Utility to access the io_context from the service.
  asio::io_context& get_io_context() const { return io_context_; }

  explicit my_custom_service(asio::io_context& ctx) : io_context_(ctx) {}

private:
  asio::io_context& io_context_;
};

} // namespace my_namespace

```

The service automatically registers with the `io_context` through `asio::use_service`, which creates one service instance per `io_context`.

## Creating the Public I/O Object Wrapper

Derive your public class from `asio::basic_io_object<YourService>`. This base class provides `get_io_context()`, `get_executor()`, and protected access to `get_implementation()`.

```cpp
#pragma once
#include <asio.hpp>
#include "my_custom_service.hpp"

namespace my_namespace {

class my_custom_io
  : public asio::basic_io_object<my_custom_service>
{
public:
  // Expose the service's implementation for internal helpers.
  using implementation_type = service_type::implementation_type;

  // ctor forwards the io_context to the base class.
  explicit my_custom_io(asio::io_context& ctx)
    : asio::basic_io_object<my_custom_service>(ctx)
  {}

  // Example synchronous operation.
  void write_some(const char* data, std::size_t size)
  {
    const implementation_type& impl = this->get_implementation();
    ::write(impl.native_handle, data, size);
  }

  // Example asynchronous operation using the generic async initiation pattern.
  template <typename CompletionToken>
  auto async_write_some(const char* data,
                        std::size_t size,
                        CompletionToken&& token)
  {
    // The async result type deduces the handler signature.
    using handler_type = typename asio::async_result<CompletionToken,
                         void(asio::error_code, std::size_t)>::completion_handler_type;

    // Initiate the operation on the executor associated with the object.
    return asio::async_initiate<CompletionToken,
                                 void(asio::error_code, std::size_t)>(
        [&](handler_type&& handler)
        {
          // Here we could post the operation to the executor.
          this->get_executor().post(
            [this, data, size, h = std::move(handler)]() mutable
            {
              const implementation_type& impl = this->get_implementation();
              ssize_t bytes = ::write(impl.native_handle, data, size);
              if (bytes < 0)
                h(asio::error_code(errno, asio::error::get_system_category()), 0);
              else
                h(asio::error_code{}, static_cast<std::size_t>(bytes));
            });
        },
        std::forward<CompletionToken>(token));
  }
};

} // namespace my_namespace

```

The `service_type` typedef is automatically provided by `basic_io_object`, exposing your service's `implementation_type` to the wrapper class.

## Complete Usage Example

Integrate your custom I/O object with standard ASIO patterns:

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

int main()
{
  asio::io_context ctx;

  // Construct the custom object.
  my_namespace::my_custom_io my_io(ctx);

  // Synchronous write.
  const char* msg = "Hello ASIO\n";
  my_io.write_some(msg, std::strlen(msg));

  // Asynchronous write with a lambda handler.
  my_io.async_write_some(msg, std::strlen(msg),
      [&](asio::error_code ec, std::size_t bytes)
      {
        if (!ec)
          std::printf("Async write succeeded, %zu bytes\n", bytes);
        else
          std::printf("Async write error: %s\n", ec.message().c_str());
      });

  ctx.run();          // Drive the asynchronous operation.
}

```

This example demonstrates automatic resource management—the service's `construct` and `destroy` methods handle the file descriptor lifecycle, while the async operation integrates with the `io_context` executor.

## Summary

- **Custom ASIO I/O objects** require a **service class** defining `implementation_type`, `construct()`, and `destroy()`, plus a **wrapper class** inheriting from `asio::basic_io_object<YourService>`.
- The service pattern in [`include/asio/basic_io_object.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_io_object.hpp) handles resource lifetime and move semantics automatically.
- **Synchronous operations** access native handles through `get_implementation()`.
- **Asynchronous operations** use `asio::async_initiate` to support any completion token type (lambdas, futures, coroutines).
- Reference implementations in [`include/asio/basic_socket.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_socket.hpp) demonstrate production-quality service design.

## Frequently Asked Questions

### What is the minimum set of methods required for a custom ASIO service?

A valid ASIO service must define `using implementation_type = ...;` to specify the native resource type, plus `void construct(implementation_type&)` to initialize resources and `void destroy(implementation_type&)` to clean them up. You must also provide a constructor accepting `asio::io_context&` and implement `get_io_context()` to return the stored context reference.

### How does move semantics work in custom ASIO I/O objects?

Move semantics are optional but recommended. When you implement `move_construct()` and `move_assign()` in your service, ASIO detects these methods (via the `service_has_move` trait in [`basic_io_object.hpp`](https://github.com/chriskohlhoff/asio/blob/main/basic_io_object.hpp)) and enables move operations for the I/O object. The base class calls your service's move methods when the I/O object is moved, allowing you to transfer native handles between instances.

### Can custom ASIO I/O objects be used with coroutines and co_spawn?

Yes. By implementing asynchronous operations using `asio::async_initiate`, your custom I/O object automatically supports any ASIO completion token, including coroutines. The `async_result` template deduces the appropriate handler type, allowing you to use `co_spawn` with `asio::use_awaitable` or other awaitable types.

### Where can I find reference implementations in the ASIO source code?

Study [`include/asio/basic_socket.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_socket.hpp) for a complete I/O object implementation and [`include/asio/basic_io_object.hpp`](https://github.com/chriskohlhoff/asio/blob/main/include/asio/basic_io_object.hpp) for the base class mechanics. These files demonstrate how built-in objects like sockets and timers structure their service relationships, showing production patterns for construction, destruction, and asynchronous operation initiation.