The header uvw/emitter.hpp is the beating heart of the UVW library: every resource inherits from the Emitter template to gain an event-driven interface. This article walks through the interesting language features and design choices that make the class tick.
- Type-safe event IDs without an enum
Lines 161–185 contain a small but powerful metaprogramming gadget:
static std::size_t next_type() noexcept {
static std::size_t counter = 0;
return counter++;
}
template<typename>
static std::size_t event_type() noexcept {
static std::size_t value = next_type();
return value;
}
Each distinct event type (any C++ type you pass as E) receives a unique, compile-time generated index. The trick releis on two rules:
- Every instantiation of a function template is a separate function.
- A
staticvariable inside such a function is created exactly once for that instantiation.
Consequently event_type<MyEvent>() always returns the same integer, while event_type<YourEvent>() returns the next one. The vector handlers is then resized lazily to accommodate these indices, giving O(1) lookup without a manually maintained enum.
- Move semantics and perfect forwarding
Throughout the file you will see std::move applied to listener functors when they are stored:
return onceL.emplace(onceL.cend(), false, std::move(f));
This prevents unnecessary copies of heavy std::function objects and is the canonical C++11 way to express ownership transfer. (The codebase does not use std::forward because it never deals with universal references.)
- Alias declarations with
using
Inside the nested Handler class you will find:
using Listener = std::function<void(E&, T&)>;
using Element = std::pair<bool, Listener>;
using ListenerList = std::list<Element>;
using Connection = typename ListenerList::iterator;
These aliases behave like typedef but are far more readable—especially when templates are involved. The typename keyword in the last line is required to tell the compiler that iterator is a dependent type name.
- Lifetime management inside
Handler
The concrete work of storing and dispatching listeners is done by the inner class Handler<E>. Two lists are maintained:
onL– listeners registered with.on()(fire every time).onceL– listeners registered with.once()(fire once, then auto-remove).
During publish() the onceL list is swapped into a local variable, guaranteeing that each one-shot callback is executed exactly once even if it re-registers itself from within the handler. A publishing flag prevents iterator invalidation when erase() or clear() is called from user code while the list is being traversed.
- -head guards and other small bits
#pragma onceis used instead of the classic include-guard macros; both GCC and Clang accept it.noexcepton many functions allows the compiler to generate better code by assuming no exceptions will propagate.- Member initializers such as
bool publishing{false};are C++11 brace-initialization in action.
- Threading and reuse
After stripping out the libuv-specific parts, Emitter can be dropped into any single-threaded project as a lightweight signal/slot implementation. It is not thread-safe, and its intentionally synchronous—libuv already serializes callbacks onto its own loop thread.
That covers the essentials of emitter.hpp. The next article will look at the underlying libuv types and how UVW wraps them into C++ resources.