sigslot

repository·master·Indexed 21 days ago

https://github.com/palacaze/sigslot

A header-only, thread-safe C++ signal-slot library designed as a lightweight and performant replacement for Boost.Signals2. It features object lifetime tracking, RAII connection management, slot groups for execution ordering, and support for both thread-safe (sigslot::signal) and non-thread-safe (sigslot::signal_st) signals.

Tokens
3.1K
Snippets
12
Records
15
Agent score
25%

What's inside sigslot

  1. Overview of Sigslot features

    master

    Sigslot is a thread-safe, header-only C++ signal-slot library designed as a replacement for Boost.Signals2.

    Key features include:

    • Thread safety: Safe for use in multi-threaded environments.
    • Object lifetime tracking: Automatic slot disconnection via object lifetime tracking (extensible through ADL).
    • RAII connection management: Uses RAII patterns for managing connections.
    • Slot groups: Allows enforcing specific execution orders for slots.
    • Performance: Designed for reasonable performance and a simple implementation.

    Note on Return Types: Sigslot does not support signal return types.

  2. Manage connections with sigslot::connection and sigslot::scoped_connection

    master

    sigslot::signal::connect() returns a sigslot::connection object. This is a lightweight, copyable object (similar to std::weak_ptr) used to manage the lifecycle of a specific link between a signal and a slot.

    Key features of sigslot::connection:

    • Status querying: Check if a connection is valid.
    • Blocking/Unblocking: Temporarily disable a slot without disconnecting it using .block() and .unblock().
    • Disconnection: Permanently remove the slot using .disconnect().

    For RAII-style management where the connection is automatically destroyed when the object goes out of scope, convert the sigslot::connection to a sigslot::scoped_connection.

    auto c1 = sig.connect(f);
    c1.block();  // Slot won't be called
    c1.unblock();
    c1.disconnect(); // Slot is removed
    
    // RAII approach
    {
        sigslot::scoped_connection sc = sig.connect(f);
    } // sc goes out of scope, connection is disconnected
  3. Choose between thread-safe and non-thread-safe signals

    master

    Sigslot provides two main signal types depending on your concurrency requirements:

    • sigslot::signal: Thread-safe. Uses std::mutex internally. Connection, disconnection, emission, and slot execution are safe across multiple threads. It also supports recursive signal emission.
    • sigslot::signal_st: Non-thread-safe. Trades safety for higher performance. Use this when you are certain signals will only be accessed from a single thread.
  4. Integrate Sigslot using find_package

    master

    If Sigslot is already installed on your system, use find_package to locate it and link against the Pal::Sigslot target in your CMakeLists.txt.

    # Using Sigslot from cmake
    find_package(PalSigslot)
    
    add_executable(MyExe main.cpp)
    target_link_libraries(MyExe PRIVATE Pal::Sigslot)
  5. Handle overloaded functions and default arguments in slots

    master

    Two limitations exist when connecting certain callables:

    1. Overloaded Functions: If a class has multiple functions with the same name but different signatures, the compiler cannot resolve the pointer. You must explicitly cast the function pointer using a helper like overload<Args>(...).
    2. Default Arguments: The library cannot automatically detect default arguments in function signatures. To connect a signal to a function that uses default arguments to reduce the number of arguments passed by the signal, use a lambda adapter (e.g., [=](auto && ...a) { func(std::forward<decltype(a)>(a)...); }).
    // Resolving overloads
    template <typename... Args, typename C>
    constexpr auto overload(void (C::*ptr)(Args...)) { return ptr; }
    
    // Usage
    sig.connect(overload<int>(&foo::bar), &ff);
    
    // Adapting default arguments
    #define ADAPT(func) [=](auto && ...a) { (func)(std::forward<decltype(a)>(a)...); }
    sig.connect(ADAPT(foo_with_defaults));
  6. Enable automatic slot lifetime tracking

    master

    To prevent calling destroyed objects, Sigslot can automatically disconnect slots when the underlying object is destroyed. This works if the slot object is convertible to a weak pointer.

    Supported types:

    • std::shared_ptr and std::weak_ptr (out of the box).
    • boost::shared_ptr and boost::weak_ptr (via adapters).
    • Qt QSharedPointer, QWeakPointer, and QObject derivatives (via adapters).
    • Custom types (by declaring a to_weak() adapter function).

    Alternatively, use Intrusive lifetime tracking by inheriting from sigslot::observer (thread-safe) or sigslot::observer_st (non-thread-safe).

    // Automatic tracking with shared_ptr
    auto p = std::make_shared<s>();
    sig.connect(&s::f, p);
    p.reset(); // Signal will no longer attempt to call p
    
    // Intrusive tracking
    struct s : sigslot::observer_st {
        void f(int i) { /* ... */ }
    };
  7. Integrate Sigslot using CMake FetchContent

    master

    For direct integration without a prior installation step, use CMake's FetchContent module to pull the repository directly from GitHub.

    include(FetchContent)
    
    FetchContent_Declare(
      sigslot
      GIT_REPOSITORY https://github.com/palacaze/sigslot
      GIT_TAG        19a6f0f5ea11fc121fe67f81fd5e491f2d7a4637 # v1.2.0
    )
    FetchContent_MakeAvailable(sigslot)
    
    add_executable(MyExe main.cpp)
    target_link_libraries(MyExe PRIVATE Pal::Sigslot)
  8. Install Sigslot via CMake

    master

    Sigslot is a header-only library. The preferred installation method is using CMake, which provides the Pal::Sigslot imported target. This target automatically applies necessary linker flags (such as /OPT:NOICF for MSVC/Clang-cl on Windows) to ensure compatibility.

    To install from the root directory:

    mkdir build && cd build
    cmake .. -DSIGSLOT_REDUCE_COMPILE_TIME=ON -DCMAKE_INSTALL_PREFIX=~/local
    cmake --build . --target install
  9. Configure SIGSLOT_REDUCE_COMPILE_TIME

    master

    The SIGSLOT_REDUCE_COMPILE_TIME CMake option can be used to manage the trade-off between code size/compilation speed and runtime efficiency.

    • Default (OFF): Prioritizes runtime efficiency.
    • ON: Reduces code bloat and compilation time by avoiding heavy template instantiations from std::make_shared. This results in slightly less efficient code but smaller binaries and faster builds.
  10. Use signals with arguments

    master

    Signals can be defined with multiple template arguments to emit values. Slots connected to these signals must have argument types that are convertible from the signal's argument types.

    Example: A sigslot::signal<float, int, bool, std::string&> can be connected to a slot that accepts double instead of float due to implicit conversion.

    #include <sigslot/signal.hpp>
    #include <string>
    
    struct foo {
        void bar(double d, int i, bool b, std::string &s) {
            s = b ? std::to_string(i) : std::to_string(d);
        }
    };
    
    int main() {
        sigslot::signal<float, int, bool, std::string&> sig;
        foo ff;
        sig.connect(&foo::bar, &ff);
    
        float f = 1.f;
        int i = 2;
        bool b = false;
        std::string s = "0";
    
        sig(f, i, b, s);
    }
  11. Use sigslot::signal<T...> for basic signal-slot communication

    master

    The sigslot::signal<T...> class template is the primary entry point for the library. A signal can emit typed notifications (parameters of type T...) and register multiple "slots" (callables) to be executed upon emission.

    Supported callables include:

    • Free functions
    • Member functions (via pointer and object instance)
    • Static member functions
    • Function objects (functors)
    • Lambdas (including generic lambdas)

    By default, the invocation order of slots is unspecified unless slot groups are used.

    #include <sigslot/signal.hpp>
    
    void f() { /* ... */ }
    struct s { void m(); };
    s s_inst;
    
    // Declare a signal with no arguments
    sigslot::signal<> sig;
    
    // Connect slots
    sig.connect(f);
    sig.connect(&s::m, &s_inst);
    
    // Emit the signal
    sig();
  12. Enforce slot invocation order with slot groups

    master

    By default, the order in which slots are called is unspecified. To control this, assign a sigslot::group_id to slots during connection. Slots are invoked in ascending order of their group IDs.

    • Unassigned slots default to group 0.
    • Group IDs are signed 32-bit integers.
    • Within the same group, the invocation order remains unspecified.
    // Slots will be called in order: First -> Second -> Last
    sig.connect([] { std::puts("First"); }, -10);
    sig.connect([] { std::puts("Second"); }, 1);
    sig.connect([] { std::puts("Last"); }, std::numeric_limits<sigslot::group_id>::max());