├── tests ├── libfastsignals_unit_tests │ ├── main.cpp │ ├── CMakeLists.txt │ ├── libfastsignals_unit_tests.vcxproj.filters │ ├── bind_weak_tests.cpp │ ├── libfastsignals_unit_tests.vcxproj │ ├── Function_tests.cpp │ └── signal_tests.cpp └── libfastsignals_stress_tests │ ├── main.cpp │ ├── CMakeLists.txt │ ├── libfastsignals_stress_tests.vcxproj.filters │ ├── signal_stress_tests.cpp │ └── libfastsignals_stress_tests.vcxproj ├── libfastsignals ├── CMakeLists.txt ├── include │ ├── type_traits.h │ ├── combiners.h │ ├── spin_mutex.h │ ├── msvc_autolink.h │ ├── signal_impl.h │ ├── function.h │ ├── bind_weak.h │ ├── connection.h │ ├── signal.h │ └── function_detail.h ├── libfastsignals.vcxproj.filters ├── src │ ├── signal_impl.cpp │ ├── function_detail.cpp │ └── connection.cpp └── libfastsignals.vcxproj ├── .travis.yml ├── README.md ├── CMakeLists.txt ├── LICENSE ├── libfastsignals_build_options.props ├── docs ├── simple-examples.md ├── why-fastsignals.md ├── bind_weak.md └── migration-from-boost-signals2.md ├── cmake └── functions.cmake ├── FastSignals.sln ├── .clang-format └── .gitignore /tests/libfastsignals_unit_tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch2/catch.hpp" 3 | -------------------------------------------------------------------------------- /tests/libfastsignals_stress_tests/main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch2/catch.hpp" 3 | -------------------------------------------------------------------------------- /tests/libfastsignals_stress_tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | custom_add_test_from_dir(libfastsignals_stress_tests libfastsignals) 3 | -------------------------------------------------------------------------------- /tests/libfastsignals_unit_tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | custom_add_test_from_dir(libfastsignals_unit_tests libfastsignals) 3 | custom_enable_cxx17(libfastsignals_unit_tests) 4 | target_include_directories(libfastsignals_unit_tests PRIVATE "${CMAKE_SOURCE_DIR}/tests") 5 | -------------------------------------------------------------------------------- /libfastsignals/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | file(GLOB LIBFASTSIGNALS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h") 3 | add_library(libfastsignals ${LIBFASTSIGNALS_SRC}) 4 | custom_enable_cxx17(libfastsignals) 5 | target_include_directories(libfastsignals INTERFACE "${CMAKE_SOURCE_DIR}") 6 | -------------------------------------------------------------------------------- /tests/libfastsignals_stress_tests/libfastsignals_stress_tests.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /tests/libfastsignals_unit_tests/libfastsignals_unit_tests.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /libfastsignals/include/type_traits.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace is::signals 4 | { 5 | namespace detail 6 | { 7 | 8 | template 9 | struct signal_arg 10 | { 11 | using type = const T&; 12 | }; 13 | 14 | template 15 | struct signal_arg 16 | { 17 | using type = U&; 18 | }; 19 | } // namespace detail 20 | 21 | template 22 | using signal_arg_t = typename detail::signal_arg::type; 23 | 24 | } // namespace is::signals 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: required 3 | language: cpp 4 | 5 | os: 6 | - linux 7 | 8 | compiler: 9 | - gcc 10 | # TODO: - clang 11 | 12 | env: 13 | - TARGET_CPU=amd64 BUILD_CONFIGURATION=Release CI_NAME=TRAVIS 14 | # TODO: - TARGET_CPU=x86 BUILD_CONFIGURATION=Release 15 | 16 | before_install: 17 | # build environment setup script 18 | - source build/travis-install-env-$TRAVIS_OS_NAME.sh 19 | 20 | script: 21 | # Build project 22 | - mkdir -p build && cd build 23 | - cmake .. -DCMAKE_BUILD_TYPE=$BUILD_CONFIGURATION -DTARGET_CPU=$TARGET_CPU -DBUILD_TESTING=ON 24 | - cmake --build . 25 | 26 | # Run tests 27 | - tests/libfastsignals_unit_tests/libfastsignals_unit_tests 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastSignals 2 | 3 | Yet another C++ signals and slots library 4 | 5 | * Works as drop-in replacement for Boost.Signals2 with the same API 6 | * Has better performance and more compact binary code 7 | * Thread-safe in most operations, including concurrent connects/disconnects/emits 8 | * Implemented with compact, pure C++17 code 9 | 10 | [![Build Status](https://travis-ci.org/ispringteam/FastSignals.svg?branch=master)](https://travis-ci.org/ispringteam/FastSignals) 11 | [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT) 12 | 13 | ## Documentation 14 | 15 | * [Why FastSignals?](docs/why-fastsignals.md) 16 | * [Simple Examples](docs/simple-examples.md) 17 | * [Migration from Boost.Signals2](docs/migration-from-boost-signals2.md) 18 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | project(FastSignals) 4 | 5 | # Use `-DBUILD_TESTING=OFF` to disable testing/benchmarking. 6 | enable_testing() 7 | 8 | include(cmake/functions.cmake) 9 | 10 | add_subdirectory(libfastsignals) 11 | 12 | if(BUILD_TESTING) 13 | # add_subdirectory(tests/benchmark) 14 | # add_subdirectory(tests/libfastsignals_bench) 15 | #add_subdirectory(tests/libfastsignals_stress_tests) 16 | add_subdirectory(tests/libfastsignals_unit_tests) 17 | endif() 18 | 19 | # Trace variables, both environment and internal. 20 | execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "environment") 21 | get_cmake_property(_variableNames VARIABLES) 22 | list (SORT _variableNames) 23 | foreach (_variableName ${_variableNames}) 24 | message(STATUS "${_variableName}=${${_variableName}}") 25 | endforeach() 26 | -------------------------------------------------------------------------------- /libfastsignals/include/combiners.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace is::signals 6 | { 7 | 8 | /** 9 | * This results combiner reduces results collection into last value of this collection. 10 | * In other words, it keeps only result of the last slot call. 11 | */ 12 | template 13 | class optional_last_value 14 | { 15 | public: 16 | using result_type = std::optional; 17 | 18 | template 19 | void operator()(TRef&& value) 20 | { 21 | m_result = std::forward(value); 22 | } 23 | 24 | result_type get_value() const 25 | { 26 | return m_result; 27 | } 28 | 29 | private: 30 | result_type m_result = {}; 31 | }; 32 | 33 | template <> 34 | class optional_last_value 35 | { 36 | public: 37 | using result_type = void; 38 | }; 39 | 40 | } // namespace is::signals 41 | -------------------------------------------------------------------------------- /libfastsignals/include/spin_mutex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace is::signals::detail 5 | { 6 | 7 | class spin_mutex 8 | { 9 | public: 10 | spin_mutex() = default; 11 | spin_mutex(const spin_mutex&) = delete; 12 | spin_mutex& operator=(const spin_mutex&) = delete; 13 | spin_mutex(spin_mutex&&) = delete; 14 | spin_mutex& operator=(spin_mutex&&) = delete; 15 | 16 | inline bool try_lock() noexcept 17 | { 18 | return !m_busy.test_and_set(std::memory_order_acquire); 19 | } 20 | 21 | inline void lock() noexcept 22 | { 23 | while (!try_lock()) 24 | { 25 | /* do nothing */; 26 | } 27 | } 28 | 29 | inline void unlock() noexcept 30 | { 31 | m_busy.clear(std::memory_order_release); 32 | } 33 | 34 | private: 35 | std::atomic_flag m_busy = ATOMIC_FLAG_INIT; 36 | }; 37 | 38 | } // namespace is::signals::detail 39 | -------------------------------------------------------------------------------- /libfastsignals/include/msvc_autolink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(_MSC_VER) 4 | 5 | # if defined(__clang__) 6 | # if defined(_DEBUG) && defined(_WIN64) 7 | # pragma comment(lib, "libfastsignalsd-llvm-x64.lib") 8 | # elif defined(_DEBUG) 9 | # pragma comment(lib, "libfastsignalsd-llvm-x32.lib") 10 | # elif defined(_WIN64) 11 | # pragma comment(lib, "libfastsignals-llvm-x64.lib") 12 | # else 13 | # pragma comment(lib, "libfastsignals-llvm-x32.lib") 14 | # endif 15 | # elif _MSC_VER <= 1900 16 | # error this library needs Visual Studio 2017 and higher 17 | # elif _MSC_VER < 2000 18 | # if defined(_DEBUG) && defined(_WIN64) 19 | # pragma comment(lib, "libfastsignalsd-v141-x64.lib") 20 | # elif defined(_DEBUG) 21 | # pragma comment(lib, "libfastsignalsd-v141-x32.lib") 22 | # elif defined(_WIN64) 23 | # pragma comment(lib, "libfastsignals-v141-x64.lib") 24 | # else 25 | # pragma comment(lib, "libfastsignals-v141-x32.lib") 26 | # endif 27 | # else 28 | # error unknown Visual Studio version, auto-linking setup failed 29 | # endif 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 iSpring Solutions Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /libfastsignals_build_options.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | d 7 | 8 | 9 | -x32 10 | -x64 11 | 12 | 13 | 14 | 15 | MultiThreaded 16 | false 17 | 18 | 19 | Default 20 | 21 | 22 | 23 | 24 | MultiThreadedDebug 25 | Level4 26 | true 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/simple-examples.md: -------------------------------------------------------------------------------- 1 | # Simple Examples 2 | 3 | >If you are not familar with Boost.Signals2, please read [Boost.Signals2: Connections](https://theboostcpplibraries.com/boost.signals2-connections) 4 | 5 | ## Example with signal<> and connection 6 | 7 | ```cpp 8 | // Creates signal and connects 1 slot, calls 2 times, disconnects, calls again. 9 | // Outputs: 10 | // 13 11 | // 17 12 | #include "libfastsignals/signal.h" 13 | 14 | using namespace is::signals; 15 | 16 | int main() 17 | { 18 | signal valueChanged; 19 | connection conn; 20 | conn = valueChanged.connect([](int value) { 21 | cout << value << endl; 22 | }); 23 | valueChanged(13); 24 | valueChanged(17); 25 | conn.disconnect(); 26 | valueChanged(42); 27 | } 28 | ``` 29 | 30 | ## Example with scoped_connection 31 | 32 | ```cpp 33 | // Creates signal and connects 1 slot, calls 2 times, calls again after scoped_connection destroyed. 34 | // - note: scoped_connection closes connection in destructor 35 | // Outputs: 36 | // 13 37 | // 17 38 | #include "libfastsignals/signal.h" 39 | 40 | using namespace is::signals; 41 | 42 | int main() 43 | { 44 | signal valueChanged; 45 | { 46 | scoped_connection conn; 47 | conn = valueChanged.connect([](int value) { 48 | cout << value << endl; 49 | }); 50 | valueChanged(13); 51 | valueChanged(17); 52 | } 53 | valueChanged(42); 54 | } 55 | ``` 56 | -------------------------------------------------------------------------------- /libfastsignals/include/signal_impl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "function_detail.h" 4 | #include "spin_mutex.h" 5 | #include 6 | #include 7 | 8 | namespace is::signals::detail 9 | { 10 | 11 | class signal_impl 12 | { 13 | public: 14 | uint64_t add(packed_function fn); 15 | 16 | void remove(uint64_t id) noexcept; 17 | 18 | void remove_all() noexcept; 19 | 20 | size_t count() const noexcept; 21 | 22 | template 23 | Result invoke(Args... args) const 24 | { 25 | packed_function slot; 26 | size_t slotIndex = 0; 27 | uint64_t slotId = 1; 28 | 29 | if constexpr (std::is_same_v) 30 | { 31 | while (get_next_slot(slot, slotIndex, slotId)) 32 | { 33 | slot.get()(std::forward(args)...); 34 | } 35 | } 36 | else 37 | { 38 | Combiner combiner; 39 | while (get_next_slot(slot, slotIndex, slotId)) 40 | { 41 | combiner(slot.get()(std::forward(args)...)); 42 | } 43 | return combiner.get_value(); 44 | } 45 | } 46 | 47 | private: 48 | bool get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const; 49 | 50 | mutable spin_mutex m_mutex; 51 | std::vector m_functions; 52 | std::vector m_ids; 53 | uint64_t m_nextId = 1; 54 | }; 55 | 56 | using signal_impl_ptr = std::shared_ptr; 57 | using signal_impl_weak_ptr = std::weak_ptr; 58 | 59 | } // namespace is::signals::detail 60 | -------------------------------------------------------------------------------- /docs/why-fastsignals.md: -------------------------------------------------------------------------------- 1 | # Why FastSignals? 2 | 3 | FastSignals is a C++17 signals/slots implementation which API is compatible with Boost.Signals2. 4 | 5 | FastSignals pros: 6 | 7 | * Faster than Boost.Signals2 8 | * Has more compact binary code 9 | * Has the same API as Boost.Signals2 10 | 11 | FastSignals cons: 12 | 13 | * Supports only C++17 compatible compilers: Visual Studio 2017, modern Clang, modern GCC 14 | * Lacks a few rarely used features presented in Boost.Signals2 15 | * No access to connection from slot with `signal::connect_extended` method 16 | * No connected object tracking with `slot::track` method 17 | * Use [bind_weak](bind_weak.md) instead 18 | * No temporary signal blocking with `shared_connection_block` class 19 | * Cannot disconnect equivalent slots since no `disconnect(slot)` function overload 20 | * Any other API difference is a bug - please report it! 21 | 22 | See also [Migration from Boost.Signals2](migration-from-boost-signals2.md). 23 | 24 | ## Benchmark results 25 | 26 | Directory `tests/libfastsignals_bench` contains simple benchmark with compares two signal/slot implementations: 27 | 28 | * Boost.Signals2 29 | * libfastsignals 30 | 31 | Benchmark compairs performance when signal emitted frequently with 0, 1 and 8 active connections. In these cases libfastsignals is 3-6 times faster. 32 | 33 | ``` 34 | *** Results: 35 | measure emit_boost emit_fastsignals 36 | emit_boost/0 1.00 3.00 37 | emit_boost/1 1.00 5.76 38 | emit_boost/8 1.00 3.70 39 | *** 40 | ``` 41 | -------------------------------------------------------------------------------- /libfastsignals/include/function.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "function_detail.h" 4 | 5 | namespace is::signals 6 | { 7 | // Derive your class from not_directly_callable to prevent function from wrapping it using its template constructor 8 | // Useful if your class provides custom operator for casting to function 9 | struct not_directly_callable 10 | { 11 | }; 12 | 13 | template 14 | using enable_if_callable_t = typename std::enable_if_t< 15 | !std::is_same_v, Function> && !std::is_base_of_v> && std::is_same_v, Return>>; 16 | 17 | template 18 | class function; 19 | 20 | // Compact function class - causes minimal code bloat when compiled. 21 | // Replaces std::function in this library. 22 | template 23 | class function 24 | { 25 | public: 26 | function() = default; 27 | 28 | function(const function& other) = default; 29 | function(function&& other) noexcept = default; 30 | function& operator=(const function& other) = default; 31 | function& operator=(function&& other) noexcept = default; 32 | 33 | template , Return, Arguments...>> 34 | function(Fn&& function) noexcept(detail::is_noexcept_packed_function_init) 35 | { 36 | m_packed.init(std::forward(function)); 37 | } 38 | 39 | Return operator()(Arguments&&... args) const 40 | { 41 | auto& proxy = m_packed.get(); 42 | return proxy(std::forward(args)...); 43 | } 44 | 45 | detail::packed_function release() noexcept 46 | { 47 | return std::move(m_packed); 48 | } 49 | 50 | private: 51 | detail::packed_function m_packed; 52 | }; 53 | 54 | } // namespace is::signals 55 | -------------------------------------------------------------------------------- /cmake/functions.cmake: -------------------------------------------------------------------------------- 1 | # В текущей версии CMake не может включить режим C++17 в некоторых компиляторах. 2 | # Функция использует обходной манёвр. 3 | function(custom_enable_cxx17 TARGET) 4 | # Включаем C++17 везде, где CMake может. 5 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 6 | # Включаем режим C++latest в Visual Studio 7 | if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 8 | set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "/std:c++latest") 9 | # Включаем компоновку с libc++, libc++experimental и pthread для Clang 10 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 11 | set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "-stdlib=libc++ -pthread") 12 | target_link_libraries(${TARGET} c++experimental pthread) 13 | endif() 14 | endfunction(custom_enable_cxx17) 15 | 16 | # Функция добавляет цель-библиотеку. 17 | function(custom_add_library_from_dir TARGET) 18 | # Собираем файлы с текущего каталога 19 | file(GLOB TARGET_SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.h") 20 | add_library(${TARGET} ${TARGET_SRC}) 21 | endfunction() 22 | 23 | # Функция добавляет цель исполняемого файла. 24 | function(custom_add_executable_from_dir TARGET) 25 | # Собираем файлы с текущего каталога 26 | file(GLOB TARGET_SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.h") 27 | add_executable(${TARGET} ${TARGET_SRC}) 28 | endfunction() 29 | 30 | # Функция добавляет цель исполняемого файла, содержащего тесты библиотеки. 31 | function(custom_add_test_from_dir TARGET LIBRARY) 32 | custom_add_executable_from_dir(${TARGET}) 33 | # Добавляем путь к заголовку фреймворка Catch 34 | target_include_directories(${TARGET} PRIVATE "${CMAKE_SOURCE_DIR}/libs/catch") 35 | # Добавляем компоновку с проверяемой библиотекой 36 | target_link_libraries(${TARGET} ${LIBRARY}) 37 | # Регистрируем исполняемый файл в CMake как набор тестов. 38 | add_test(${TARGET} ${TARGET}) 39 | endfunction() 40 | -------------------------------------------------------------------------------- /libfastsignals/libfastsignals.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | include 6 | 7 | 8 | include 9 | 10 | 11 | include 12 | 13 | 14 | include 15 | 16 | 17 | include 18 | 19 | 20 | include 21 | 22 | 23 | include 24 | 25 | 26 | include 27 | 28 | 29 | include 30 | 31 | 32 | include 33 | 34 | 35 | 36 | 37 | {474d3307-8dbe-47d6-a12f-35f944912d9d} 38 | 39 | 40 | {ac074187-2f8f-44b9-a170-24568deb06e6} 41 | 42 | 43 | 44 | 45 | src 46 | 47 | 48 | src 49 | 50 | 51 | src 52 | 53 | 54 | -------------------------------------------------------------------------------- /libfastsignals/src/signal_impl.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/signal_impl.h" 2 | #include 3 | #include 4 | 5 | namespace is::signals::detail 6 | { 7 | 8 | uint64_t signal_impl::add(packed_function fn) 9 | { 10 | std::lock_guard lock(m_mutex); 11 | 12 | m_functions.emplace_back(std::move(fn)); 13 | 14 | try 15 | { 16 | m_ids.emplace_back(m_nextId); 17 | } 18 | catch (const std::bad_alloc& /*e*/) 19 | { 20 | // Remove function since we failed to add its id 21 | m_functions.pop_back(); 22 | throw; 23 | } 24 | 25 | return m_nextId++; 26 | } 27 | 28 | void signal_impl::remove(uint64_t id) noexcept 29 | { 30 | std::lock_guard lock(m_mutex); 31 | 32 | // We use binary search because ids array is always sorted. 33 | auto it = std::lower_bound(m_ids.begin(), m_ids.end(), id); 34 | if (it != m_ids.end() && *it == id) 35 | { 36 | size_t i = std::distance(m_ids.begin(), it); 37 | m_ids.erase(m_ids.begin() + i); 38 | m_functions.erase(m_functions.begin() + i); 39 | } 40 | } 41 | 42 | void signal_impl::remove_all() noexcept 43 | { 44 | std::lock_guard lock(m_mutex); 45 | m_functions.clear(); 46 | m_ids.clear(); 47 | } 48 | 49 | bool signal_impl::get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const 50 | { 51 | // Slots always arranged by ID, so we can use a simple algorithm which avoids races: 52 | // - on each step find first slot with ID >= slotId 53 | // - after each call increment slotId 54 | 55 | std::lock_guard lock(m_mutex); 56 | 57 | // Avoid binary search if next slot wasn't moved between mutex locks. 58 | if (expectedIndex >= m_ids.size() || m_ids[expectedIndex] != nextId) 59 | { 60 | auto it = (nextId < m_nextId) 61 | ? std::lower_bound(m_ids.cbegin(), m_ids.cend(), nextId) 62 | : m_ids.end(); 63 | if (it == m_ids.end()) 64 | { 65 | return false; 66 | } 67 | expectedIndex = std::distance(m_ids.cbegin(), it); 68 | } 69 | 70 | slot.reset(); 71 | slot = m_functions[expectedIndex]; 72 | nextId = (expectedIndex + 1 < m_ids.size()) ? m_ids[expectedIndex + 1] : m_ids[expectedIndex] + 1; 73 | ++expectedIndex; 74 | return true; 75 | } 76 | 77 | size_t signal_impl::count() const noexcept 78 | { 79 | std::lock_guard lock(m_mutex); 80 | 81 | return m_functions.size(); 82 | } 83 | 84 | } // namespace is::signals::detail 85 | -------------------------------------------------------------------------------- /libfastsignals/src/function_detail.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/function_detail.h" 2 | #include 3 | #include 4 | 5 | namespace is::signals::detail 6 | { 7 | 8 | packed_function::packed_function(packed_function&& other) noexcept 9 | : m_proxy(move_proxy_from(std::move(other))) 10 | { 11 | } 12 | 13 | packed_function::packed_function(const packed_function& other) 14 | : m_proxy(clone_proxy_from(other)) 15 | { 16 | } 17 | 18 | packed_function& packed_function::operator=(packed_function&& other) noexcept 19 | { 20 | assert(this != &other); 21 | reset(); 22 | m_proxy = move_proxy_from(std::move(other)); 23 | return *this; 24 | } 25 | 26 | base_function_proxy* packed_function::move_proxy_from(packed_function&& other) noexcept 27 | { 28 | auto proxy = other.m_proxy ? other.m_proxy->move(&m_buffer) : nullptr; 29 | other.m_proxy = nullptr; 30 | return proxy; 31 | } 32 | 33 | base_function_proxy* packed_function::clone_proxy_from(const packed_function& other) 34 | { 35 | return other.m_proxy ? other.m_proxy->clone(&m_buffer) : nullptr; 36 | } 37 | 38 | packed_function& packed_function::operator=(const packed_function& other) 39 | { 40 | if (this != &other) 41 | { 42 | if (other.is_buffer_allocated() && is_buffer_allocated()) 43 | { 44 | // "This" and "other" are using SBO. Safe assignment must use copy+move 45 | *this = packed_function(other); 46 | } 47 | else 48 | { 49 | // Buffer is used either by "this" or by "other" or not used at all. 50 | // If this uses buffer then other's proxy is null or allocated on heap, so clone won't overwrite buffer 51 | // If this uses heap or null then other's proxy can safely use buffer because reset() won't access buffer 52 | auto newProxy = clone_proxy_from(other); 53 | reset(); 54 | m_proxy = newProxy; 55 | } 56 | } 57 | return *this; 58 | } 59 | 60 | packed_function::~packed_function() noexcept 61 | { 62 | reset(); 63 | } 64 | 65 | void packed_function::reset() noexcept 66 | { 67 | if (m_proxy != nullptr) 68 | { 69 | if (is_buffer_allocated()) 70 | { 71 | m_proxy->~base_function_proxy(); 72 | } 73 | else 74 | { 75 | delete m_proxy; 76 | } 77 | m_proxy = nullptr; 78 | } 79 | } 80 | 81 | base_function_proxy& packed_function::unwrap() const 82 | { 83 | if (m_proxy == nullptr) 84 | { 85 | throw std::bad_function_call(); 86 | } 87 | return *m_proxy; 88 | } 89 | 90 | bool packed_function::is_buffer_allocated() const noexcept 91 | { 92 | return std::less_equal()(&m_buffer[0], m_proxy) 93 | && std::less()(m_proxy, &m_buffer[1]); 94 | } 95 | 96 | } // namespace is::signals::detail 97 | -------------------------------------------------------------------------------- /libfastsignals/include/bind_weak.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace is::signals 4 | { 5 | namespace detail 6 | { 7 | template 8 | struct weak_binder 9 | { 10 | using ConstMethodType = ReturnType (ClassType::*)(Args... args) const; 11 | using NonConstMethodType = ReturnType (ClassType::*)(Args... args); 12 | using MethodType = std::conditional_t; 13 | using WeakPtrType = std::weak_ptr; 14 | 15 | weak_binder(MethodType pMethod, WeakPtrType&& pObject) 16 | : m_pMethod(pMethod) 17 | , m_pObject(pObject) 18 | { 19 | } 20 | 21 | ReturnType operator()(Args... args) const 22 | { 23 | if (auto pThis = m_pObject.lock()) 24 | { 25 | return (pThis.get()->*m_pMethod)(std::forward(args)...); 26 | } 27 | return ReturnType(); 28 | } 29 | 30 | MethodType m_pMethod; 31 | WeakPtrType m_pObject; 32 | }; 33 | } // namespace detail 34 | 35 | /// Weak this binding of non-const methods. 36 | template 37 | decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args), std::shared_ptr const& pThis, Args... args) 38 | { 39 | using weak_binder_alias = detail::weak_binder; 40 | 41 | weak_binder_alias invoker(memberFn, std::weak_ptr(pThis)); 42 | return std::bind(invoker, args...); 43 | } 44 | 45 | /// Weak this binding of const methods. 46 | template 47 | decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args) const, std::shared_ptr const& pThis, Args... args) 48 | { 49 | using weak_binder_alias = detail::weak_binder; 50 | 51 | weak_binder_alias invoker(memberFn, std::weak_ptr(pThis)); 52 | return std::bind(invoker, args...); 53 | } 54 | 55 | /// Weak this binding of non-const methods. 56 | template 57 | decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args), std::weak_ptr pThis, Args... args) 58 | { 59 | using weak_binder_alias = detail::weak_binder; 60 | 61 | weak_binder_alias invoker(memberFn, std::move(pThis)); 62 | return std::bind(invoker, args...); 63 | } 64 | 65 | /// Weak this binding of const methods. 66 | template 67 | decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args) const, std::weak_ptr pThis, Args... args) 68 | { 69 | using weak_binder_alias = detail::weak_binder; 70 | 71 | weak_binder_alias invoker(memberFn, std::move(pThis)); 72 | return std::bind(invoker, args...); 73 | } 74 | 75 | } // namespace is::signals 76 | -------------------------------------------------------------------------------- /docs/bind_weak.md: -------------------------------------------------------------------------------- 1 | # Function bind_weak 2 | 3 | ## Usage 4 | 5 | * Use `is::signals::bind_weak` instead of `std::bind` to ensure that nothing happens if method called when binded object already destroyed 6 | * Pass pointer to T class method as first argument, `shared_ptr` or `weak_ptr` as second argument 7 | * Example: `bind_weak(&Document::save(), document, std::placeholders::_1)`, where `document` is a `weak_ptr` or `shared_ptr` 8 | 9 | ## Weak this idiom 10 | 11 | The `is::signals::bind_weak(...)` function implements "weak this" idiom. This idiom helps to avoid dangling pointers and memory access wiolations in asynchronous and/or multithreaded programs. 12 | 13 | In the following example, we use weak this idiom to avoid using dangling pointer wehn calling `print()` method of the `Enityt`: 14 | 15 | ```cpp 16 | struct Entity : std::enable_shared_from_this 17 | { 18 | int value = 42; 19 | 20 | void print() 21 | { 22 | std::cout << "print called, num = " << value << std::endl; 23 | } 24 | 25 | std::function print_later() 26 | { 27 | // ! weak this idiom here ! 28 | auto weak_this = weak_from_this(); 29 | return [weak_this] { 30 | if (auto shared_this = weak_this.lock()) 31 | { 32 | shared_this->print(); 33 | } 34 | }; 35 | } 36 | }; 37 | 38 | int main() 39 | { 40 | auto entity = std::make_shared(); 41 | auto print = entity->print_later(); 42 | 43 | // Prints OK. 44 | print(); 45 | 46 | // Prints nothing - last shared_ptr to the Entity destroyed, so `weak_this.lock()` will return nullptr. 47 | entity = nullptr; 48 | print(); 49 | } 50 | ``` 51 | 52 | ## Using bind_weak to avoid signal receiver lifetime issues 53 | 54 | In the following example, `Entity::print()` method connected to the signal. Signal emmited once before and once after the `Entity` instance destroyed. However, no memory access violation happens: once `Entity` destoryed, no slot will be called because `bind_weak` doesn't call binded method if it cannot lock `std::weak_ptr` to binded object. The second `event()` expression just does nothing. 55 | 56 | ```cpp 57 | #include "fastsignals/signal.h" 58 | #include "fastsignals/bind_weak.h" 59 | #include 60 | 61 | using VoidSignal = is::signals::signal; 62 | using VoidSlot = VoidSignal::slot_type; 63 | 64 | struct Entity : std::enable_shared_from_this 65 | { 66 | int value = 42; 67 | 68 | VoidSlot get_print_slot() 69 | { 70 | // Here is::signals::bind_weak() used instead of std::bind. 71 | return is::signals::bind_weak(&Entity::print, weak_from_this()); 72 | } 73 | 74 | void print() 75 | { 76 | std::cout << "print called, num = " << value << std::endl; 77 | } 78 | }; 79 | 80 | int main() 81 | { 82 | VoidSignal event; 83 | auto entity = std::make_shared(); 84 | event.connect(entity->get_print_slot()); 85 | 86 | // Here slot called - it prints `slot called, num = 42` 87 | event(); 88 | entity = nullptr; 89 | 90 | // Here nothing happens - no exception, no slot call. 91 | event(); 92 | } 93 | 94 | ``` 95 | -------------------------------------------------------------------------------- /tests/libfastsignals_stress_tests/signal_stress_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "catch2/catch.hpp" 2 | #include "libfastsignals/include/signal.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace is::signals; 9 | 10 | namespace 11 | { 12 | using string_signal = signal; 13 | using string_slot = string_signal::slot_type; 14 | using void_signal = signal; 15 | using void_slot = void_signal::slot_type; 16 | 17 | class named_entity 18 | { 19 | public: 20 | std::string name() const 21 | { 22 | std::lock_guard lock(m_nameMutex); 23 | return m_name; 24 | } 25 | 26 | void fire_changed(std::string value) 27 | { 28 | bool fire = false; 29 | { 30 | std::lock_guard lock(m_nameMutex); 31 | if (m_name != value) 32 | { 33 | m_name = std::move(value); 34 | fire = true; 35 | } 36 | } 37 | if (fire) 38 | { 39 | m_nameChanged(value); 40 | } 41 | } 42 | 43 | connection on_name_changed(string_slot slot) 44 | { 45 | return m_nameChanged.connect(std::move(slot)); 46 | } 47 | 48 | private: 49 | mutable std::mutex m_nameMutex; 50 | std::string m_name; 51 | signal m_nameChanged; 52 | }; 53 | 54 | unsigned get_next_seed() 55 | { 56 | static std::minstd_rand seedEngine(777); 57 | return seedEngine(); 58 | } 59 | 60 | size_t get_random_index(size_t size) 61 | { 62 | thread_local std::minstd_rand disconnectRandomEngine{ get_next_seed() }; 63 | std::uniform_int_distribution disconnectIndexDistribution{ 0, size - 1 }; 64 | 65 | return disconnectIndexDistribution(disconnectRandomEngine); 66 | } 67 | } // namespace 68 | 69 | TEST_CASE("Can work in a few threads", "[signal]") 70 | { 71 | constexpr unsigned fireThreadCount = 8; 72 | constexpr unsigned signalsCount = 7; 73 | constexpr unsigned fireCountPerThread = 100'000; 74 | constexpr unsigned connectCallsCount = 80'000; 75 | constexpr unsigned totalRunCount = 10; 76 | 77 | for (unsigned i = 0; i < totalRunCount; ++i) 78 | { 79 | std::array signals; 80 | 81 | std::mutex connectionsMutex; 82 | std::vector connections; 83 | connections.reserve(connectCallsCount); 84 | 85 | std::vector threads; 86 | 87 | auto slot = [&] { 88 | std::lock_guard lock(connectionsMutex); 89 | if (!connections.empty()) 90 | { 91 | const size_t index = get_random_index(connections.size()); 92 | connections.at(index).disconnect(); 93 | } 94 | }; 95 | 96 | threads.emplace_back([&] { 97 | for (unsigned cci = 0; cci < connectCallsCount; ++cci) 98 | { 99 | const size_t index = get_random_index(signalsCount); 100 | connection conn = signals.at(index).connect(slot); 101 | { 102 | std::lock_guard lock(connectionsMutex); 103 | connections.emplace_back(conn); 104 | } 105 | } 106 | }); 107 | 108 | for (unsigned fti = 0; fti < fireThreadCount; ++fti) 109 | { 110 | threads.emplace_back([&] { 111 | for (unsigned fi = 0; fi < fireCountPerThread; ++fi) 112 | { 113 | const size_t index = get_random_index(signalsCount); 114 | signals.at(index)(); 115 | } 116 | }); 117 | } 118 | for (auto& thread : threads) 119 | { 120 | thread.join(); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /FastSignals.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2035 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals", "libfastsignals\libfastsignals.vcxproj", "{32BD918F-EDBC-4057-A033-10DC361DA4A0}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals_unit_tests", "tests\libfastsignals_unit_tests\libfastsignals_unit_tests.vcxproj", "{BAC23A51-8DC1-4589-940F-9923D8E12718}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals_stress_tests", "tests\libfastsignals_stress_tests\libfastsignals_stress_tests.vcxproj", "{751DC150-1907-4D9F-8566-AA4E24FDFA64}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|x64 = Debug|x64 17 | Debug|x86 = Debug|x86 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x64.ActiveCfg = Debug|x64 23 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x64.Build.0 = Debug|x64 24 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x86.ActiveCfg = Debug|Win32 25 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x86.Build.0 = Debug|Win32 26 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x64.ActiveCfg = Release|x64 27 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x64.Build.0 = Release|x64 28 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x86.ActiveCfg = Release|Win32 29 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x86.Build.0 = Release|Win32 30 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x64.ActiveCfg = Debug|x64 31 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x64.Build.0 = Debug|x64 32 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x86.ActiveCfg = Debug|Win32 33 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x86.Build.0 = Debug|Win32 34 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x64.ActiveCfg = Release|x64 35 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x64.Build.0 = Release|x64 36 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x86.ActiveCfg = Release|Win32 37 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x86.Build.0 = Release|Win32 38 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x64.ActiveCfg = Debug|x64 39 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x64.Build.0 = Debug|x64 40 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x86.ActiveCfg = Debug|Win32 41 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x86.Build.0 = Debug|Win32 42 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x64.ActiveCfg = Release|x64 43 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x64.Build.0 = Release|x64 44 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x86.ActiveCfg = Release|Win32 45 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x86.Build.0 = Release|Win32 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {BAC23A51-8DC1-4589-940F-9923D8E12718} = {6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033} 52 | {751DC150-1907-4D9F-8566-AA4E24FDFA64} = {6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033} 53 | EndGlobalSection 54 | GlobalSection(ExtensibilityGlobals) = postSolution 55 | SolutionGuid = {12A1931D-508E-41C2-BAC6-B68CC62A710E} 56 | EndGlobalSection 57 | EndGlobal 58 | -------------------------------------------------------------------------------- /tests/libfastsignals_unit_tests/bind_weak_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "catch2/catch.hpp" 2 | #include "libfastsignals/include/bind_weak.h" 3 | 4 | using namespace is::signals; 5 | 6 | namespace 7 | { 8 | class Testbed 9 | { 10 | public: 11 | Testbed(unsigned& counter) 12 | : m_pCounter(&counter) 13 | { 14 | } 15 | 16 | void IncrementNonConst() 17 | { 18 | ++(*m_pCounter); 19 | } 20 | 21 | void IncrementsConst() const 22 | { 23 | ++(*m_pCounter); 24 | } 25 | 26 | int ReflectInt(int value) const 27 | { 28 | return value; 29 | } 30 | 31 | private: 32 | unsigned* m_pCounter = nullptr; 33 | }; 34 | } // namespace 35 | 36 | TEST_CASE("can bind const methods", "[bind_weak]") 37 | { 38 | unsigned counter = 0; 39 | auto pSharedBed = std::make_shared(counter); 40 | auto boundFn = bind_weak(&Testbed::IncrementNonConst, pSharedBed); 41 | REQUIRE(counter == 0u); 42 | boundFn(); 43 | REQUIRE(counter == 1u); 44 | boundFn(); 45 | REQUIRE(counter == 2u); 46 | pSharedBed = nullptr; 47 | boundFn(); 48 | REQUIRE(counter == 2u); 49 | boundFn(); 50 | REQUIRE(counter == 2u); 51 | } 52 | 53 | TEST_CASE("can bind non const methods", "[bind_weak]") 54 | { 55 | unsigned counter = 0; 56 | auto pSharedBed = std::make_shared(counter); 57 | auto boundFn = bind_weak(&Testbed::IncrementsConst, pSharedBed); 58 | REQUIRE(counter == 0u); 59 | boundFn(); 60 | REQUIRE(counter == 1u); 61 | boundFn(); 62 | REQUIRE(counter == 2u); 63 | pSharedBed = nullptr; 64 | boundFn(); 65 | REQUIRE(counter == 2u); 66 | boundFn(); 67 | REQUIRE(counter == 2u); 68 | } 69 | 70 | TEST_CASE("can bind method with argument value", "[bind_weak]") 71 | { 72 | unsigned counter = 0; 73 | auto pSharedBed = std::make_shared(counter); 74 | auto boundFn = bind_weak(&Testbed::ReflectInt, pSharedBed, 42); 75 | REQUIRE(boundFn() == 42); 76 | REQUIRE(boundFn() == 42); 77 | pSharedBed = nullptr; 78 | REQUIRE(boundFn() == 0); 79 | REQUIRE(boundFn() == 0); 80 | } 81 | 82 | TEST_CASE("copies value when bind method with argument const reference value", "[bind_weak]") 83 | { 84 | unsigned counter = 0; 85 | auto pSharedBed = std::make_shared(counter); 86 | auto makeBoundFn = [&]() { 87 | int value = 15; 88 | const int& valueRef = value; 89 | auto result = bind_weak(&Testbed::ReflectInt, pSharedBed, valueRef); 90 | value = 25; 91 | return result; 92 | }; 93 | 94 | auto boundFn = makeBoundFn(); 95 | REQUIRE(boundFn(42) == 15); 96 | REQUIRE(boundFn(42) == 15); 97 | pSharedBed = nullptr; 98 | REQUIRE(boundFn(42) == 0); 99 | REQUIRE(boundFn(42) == 0); 100 | } 101 | 102 | TEST_CASE("copies value when bind method with argument reference value", "[bind_weak]") 103 | { 104 | unsigned counter = 0; 105 | auto pSharedBed = std::make_shared(counter); 106 | auto makeBoundFn = [&]() { 107 | int value = 15; 108 | int& valueRef = value; 109 | auto result = bind_weak(&Testbed::ReflectInt, pSharedBed, valueRef); 110 | valueRef = 25; 111 | return result; 112 | }; 113 | 114 | auto boundFn = makeBoundFn(); 115 | REQUIRE(boundFn(42) == 15); 116 | REQUIRE(boundFn(42) == 15); 117 | pSharedBed = nullptr; 118 | REQUIRE(boundFn(42) == 0); 119 | REQUIRE(boundFn(42) == 0); 120 | } 121 | 122 | TEST_CASE("can bind method with placeholder", "[bind_weak]") 123 | { 124 | unsigned counter = 0; 125 | auto pSharedBed = std::make_shared(counter); 126 | auto boundFn = bind_weak(&Testbed::ReflectInt, pSharedBed, std::placeholders::_1); 127 | REQUIRE(boundFn(42) == 42); 128 | REQUIRE(boundFn(42) == 42); 129 | pSharedBed = nullptr; 130 | REQUIRE(boundFn(42) == 0); 131 | REQUIRE(boundFn(42) == 0); 132 | } 133 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlinesLeft: false 9 | AlignOperands: false 10 | AlignTrailingComments: false 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: true 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: false 30 | AfterStruct: true 31 | AfterUnion: true 32 | BeforeCatch: true 33 | BeforeElse: true 34 | IndentBraces: false 35 | BreakBeforeBinaryOperators: All 36 | BreakBeforeBraces: Custom 37 | BreakBeforeTernaryOperators: true 38 | BreakConstructorInitializersBeforeComma: true 39 | BreakAfterJavaFieldAnnotations: false 40 | BreakStringLiterals: true 41 | ColumnLimit: 0 42 | CommentPragmas: '^ IWYU pragma:' 43 | BreakBeforeInheritanceComma: true 44 | FixNamespaceComments: true 45 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 46 | ConstructorInitializerIndentWidth: 4 47 | ContinuationIndentWidth: 4 48 | Cpp11BracedListStyle: false 49 | DerivePointerAlignment: false 50 | DisableFormat: false 51 | ExperimentalAutoDetectBinPacking: false 52 | # FixNamespaceComments: false 53 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 54 | IncludeCategories: 55 | - Regex: '^"(stdafx|PrecompiledHeader)' 56 | Priority: -2 57 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 58 | Priority: 4 59 | - Regex: '^(<|"(gtest|isl|json)/)' 60 | Priority: 3 61 | - Regex: '.*' 62 | Priority: 2 63 | IncludeIsMainRegex: '$' 64 | IndentCaseLabels: false 65 | IndentWidth: 4 66 | IndentWrappedFunctionNames: false 67 | JavaScriptQuotes: Leave 68 | JavaScriptWrapImports: true 69 | KeepEmptyLinesAtTheStartOfBlocks: true 70 | MacroBlockBegin: 'BEGIN_MESSAGE_MAP_CUSTOM$|BEGIN_MSG_MAP$|BEGIN_SINK_MAP$|BEGIN_MESSAGE_MAP$|BOOST_FIXTURE_TEST_SUITE$|BOOST_AUTO_TEST_SUITE$|BEGIN_DLGRESIZE_MAP$|BEGIN_MSG_MAP_EX$|BEGIN_DDX_MAP$|BEGIN_COM_MAP$|BEGIN_CONNECTION_POINT_MAP$|WTL_BEGIN_LAYOUT_MAP$|WTL_BEGIN_LAYOUT_CONTAINER$' 71 | MacroBlockEnd: 'END_MESSAGE_MAP_CUSTOM$|END_MSG_MAP$|END_SINK_MAP$|END_MESSAGE_MAP$|BOOST_AUTO_TEST_SUITE_END$|END_DLGRESIZE_MAP$|END_DDX_MAP$|END_COM_MAP$|END_CONNECTION_POINT_MAP$|WTL_END_LAYOUT_MAP$|WTL_END_LAYOUT_CONTAINER$' 72 | MaxEmptyLinesToKeep: 1 73 | NamespaceIndentation: None 74 | ObjCBlockIndentWidth: 4 75 | ObjCSpaceAfterProperty: true 76 | ObjCSpaceBeforeProtocolList: true 77 | PenaltyBreakBeforeFirstCallParameter: 19 78 | PenaltyBreakComment: 300 79 | PenaltyBreakFirstLessLess: 120 80 | PenaltyBreakString: 1000 81 | PenaltyExcessCharacter: 100 82 | PenaltyReturnTypeOnItsOwnLine: 10000000 83 | PointerAlignment: Left 84 | ReflowComments: true 85 | SortIncludes: true 86 | SortUsingDeclarations: true 87 | SpaceAfterCStyleCast: false 88 | SpaceAfterTemplateKeyword: true 89 | SpaceBeforeAssignmentOperators: true 90 | SpaceBeforeParens: ControlStatements 91 | SpaceInEmptyParentheses: false 92 | SpacesBeforeTrailingComments: 1 93 | SpacesInAngles: false 94 | SpacesInContainerLiterals: true 95 | SpacesInCStyleCastParentheses: false 96 | SpacesInParentheses: false 97 | SpacesInSquareBrackets: false 98 | Standard: Cpp11 99 | TabWidth: 4 100 | UseTab: Always 101 | IndentPPDirectives: AfterHash 102 | ... 103 | -------------------------------------------------------------------------------- /libfastsignals/include/connection.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "signal_impl.h" 4 | 5 | namespace is::signals 6 | { 7 | 8 | // Connection keeps link between signal and slot and can disconnect them. 9 | // Disconnect operation is thread-safe: any thread can disconnect while 10 | // slots called on other thread. 11 | // This class itself is not thread-safe: you can't use the same connection 12 | // object from different threads at the same time. 13 | class connection 14 | { 15 | public: 16 | connection() noexcept; 17 | explicit connection(detail::signal_impl_weak_ptr storage, uint64_t id) noexcept; 18 | connection(const connection& other) noexcept; 19 | connection& operator=(const connection& other) noexcept; 20 | connection(connection&& other) noexcept; 21 | connection& operator=(connection&& other) noexcept; 22 | 23 | bool connected() const noexcept; 24 | void disconnect() noexcept; 25 | 26 | protected: 27 | detail::signal_impl_weak_ptr m_storage; 28 | uint64_t m_id = 0; 29 | }; 30 | 31 | // Connection class that supports blocking callback execution 32 | class advanced_connection : public connection 33 | { 34 | public: 35 | struct advanced_connection_impl 36 | { 37 | void block() noexcept; 38 | void unblock() noexcept; 39 | bool is_blocked() const noexcept; 40 | 41 | private: 42 | std::atomic m_blockCounter = ATOMIC_VAR_INIT(0); 43 | }; 44 | using impl_ptr = std::shared_ptr; 45 | 46 | advanced_connection() noexcept; 47 | explicit advanced_connection(connection&& conn, impl_ptr&& impl) noexcept; 48 | advanced_connection(const advanced_connection&) noexcept; 49 | advanced_connection& operator=(const advanced_connection&) noexcept; 50 | advanced_connection(advanced_connection&& other) noexcept; 51 | advanced_connection& operator=(advanced_connection&& other) noexcept; 52 | 53 | protected: 54 | impl_ptr m_impl; 55 | }; 56 | 57 | // Blocks advanced connection, so its callback will not be executed 58 | class shared_connection_block 59 | { 60 | public: 61 | shared_connection_block(const advanced_connection& connection = advanced_connection(), bool initially_blocked = true) noexcept; 62 | shared_connection_block(const shared_connection_block& other) noexcept; 63 | shared_connection_block(shared_connection_block&& other) noexcept; 64 | shared_connection_block& operator=(const shared_connection_block& other) noexcept; 65 | shared_connection_block& operator=(shared_connection_block&& other) noexcept; 66 | ~shared_connection_block(); 67 | 68 | void block() noexcept; 69 | void unblock() noexcept; 70 | bool blocking() const noexcept; 71 | 72 | private: 73 | void increment_if_blocked() const noexcept; 74 | 75 | std::weak_ptr m_connection; 76 | std::atomic m_blocked = ATOMIC_VAR_INIT(false); 77 | }; 78 | 79 | // Scoped connection keeps link between signal and slot and disconnects them in destructor. 80 | // Scoped connection is movable, but not copyable. 81 | class scoped_connection : public connection 82 | { 83 | public: 84 | scoped_connection() noexcept; 85 | scoped_connection(const connection& conn) noexcept; 86 | scoped_connection(connection&& conn) noexcept; 87 | scoped_connection(const advanced_connection& conn) = delete; 88 | scoped_connection(advanced_connection&& conn) noexcept = delete; 89 | scoped_connection(const scoped_connection&) = delete; 90 | scoped_connection& operator=(const scoped_connection&) = delete; 91 | scoped_connection(scoped_connection&& other) noexcept; 92 | scoped_connection& operator=(scoped_connection&& other) noexcept; 93 | ~scoped_connection(); 94 | 95 | connection release() noexcept; 96 | }; 97 | 98 | // scoped connection for advanced connections 99 | class advanced_scoped_connection : public advanced_connection 100 | { 101 | public: 102 | advanced_scoped_connection() noexcept; 103 | advanced_scoped_connection(const advanced_connection& conn) noexcept; 104 | advanced_scoped_connection(advanced_connection&& conn) noexcept; 105 | advanced_scoped_connection(const advanced_scoped_connection&) = delete; 106 | advanced_scoped_connection& operator=(const advanced_scoped_connection&) = delete; 107 | advanced_scoped_connection(advanced_scoped_connection&& other) noexcept; 108 | advanced_scoped_connection& operator=(advanced_scoped_connection&& other) noexcept; 109 | ~advanced_scoped_connection(); 110 | 111 | advanced_connection release() noexcept; 112 | }; 113 | 114 | } // namespace is::signals 115 | -------------------------------------------------------------------------------- /libfastsignals/include/signal.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "combiners.h" 4 | #include "connection.h" 5 | #include "function.h" 6 | #include "signal_impl.h" 7 | #include "type_traits.h" 8 | #include 9 | 10 | #if defined(_MSC_VER) 11 | # include "msvc_autolink.h" 12 | #endif 13 | 14 | namespace is::signals 15 | { 16 | template class Combiner = optional_last_value> 17 | class signal; 18 | 19 | struct advanced_tag 20 | { 21 | }; 22 | 23 | /// Signal allows to fire events to many subscribers (slots). 24 | /// In other words, it implements one-to-many relation between event and listeners. 25 | /// Signal implements observable object from Observable pattern. 26 | template class Combiner> 27 | class signal : private not_directly_callable 28 | { 29 | public: 30 | using signature_type = Return(signal_arg_t...); 31 | using slot_type = function; 32 | using combiner_type = Combiner; 33 | using result_type = typename combiner_type::result_type; 34 | 35 | signal() 36 | : m_slots(std::make_shared()) 37 | { 38 | } 39 | 40 | /// No copy construction 41 | signal(const signal&) = delete; 42 | 43 | /// Moves signal from other. Any operations on other except destruction, move, and swap are invalid 44 | signal(signal&& other) = default; 45 | 46 | /// No copy assignment 47 | signal& operator=(const signal&) = delete; 48 | 49 | /// Moves signal from other. Any operations on other except destruction, move, and swap are invalid 50 | signal& operator=(signal&& other) = default; 51 | 52 | /** 53 | * connect(slot) method subscribes slot to signal emission event. 54 | * Each time you call signal as functor, all slots are also called with given arguments. 55 | * @returns connection - object which manages signal-slot connection lifetime 56 | */ 57 | connection connect(slot_type slot) 58 | { 59 | const uint64_t id = m_slots->add(slot.release()); 60 | return connection(m_slots, id); 61 | } 62 | 63 | /** 64 | * connect(slot, advanced_tag) method subscribes slot to signal emission event with the ability to temporarily block slot execution 65 | * Each time you call signal as functor, all non-blocked slots are also called with given arguments. 66 | * You can temporarily block slot execution using shared_connection_block 67 | * @returns advanced_connection - object which manages signal-slot connection lifetime 68 | */ 69 | advanced_connection connect(slot_type slot, advanced_tag) 70 | { 71 | static_assert(std::is_void_v, "Advanced connect can only be used with slots returning void (implementation limitation)"); 72 | auto conn_impl = std::make_shared(); 73 | slot_type slot_impl = [this, slot, weak_conn_impl = std::weak_ptr(conn_impl)](signal_arg_t... args) { 74 | auto conn_impl = weak_conn_impl.lock(); 75 | if (!conn_impl || !conn_impl->is_blocked()) 76 | { 77 | slot(args...); 78 | } 79 | }; 80 | auto conn = connect(std::move(slot_impl)); 81 | return advanced_connection(std::move(conn), std::move(conn_impl)); 82 | } 83 | 84 | /** 85 | * disconnect_all_slots() method disconnects all slots from signal emission event. 86 | */ 87 | void disconnect_all_slots() noexcept 88 | { 89 | m_slots->remove_all(); 90 | } 91 | 92 | /** 93 | * num_slots() method returns number of slots attached to this singal 94 | */ 95 | [[nodiscard]] std::size_t num_slots() const noexcept 96 | { 97 | return m_slots->count(); 98 | } 99 | 100 | /** 101 | * empty() method returns true if signal has any slots attached 102 | */ 103 | [[nodiscard]] bool empty() const noexcept 104 | { 105 | return m_slots->count() == 0; 106 | } 107 | 108 | /** 109 | * operator(args...) calls all slots connected to this signal. 110 | * Logically, it fires signal emission event. 111 | */ 112 | result_type operator()(signal_arg_t... args) const 113 | { 114 | return detail::signal_impl_ptr(m_slots)->invoke...>(args...); 115 | } 116 | 117 | void swap(signal& other) noexcept 118 | { 119 | m_slots.swap(other.m_slots); 120 | } 121 | 122 | /** 123 | * Allows using signals as slots for another signal 124 | */ 125 | operator slot_type() const noexcept 126 | { 127 | return [weakSlots = detail::signal_impl_weak_ptr(m_slots)](signal_arg_t... args) { 128 | if (auto slots = weakSlots.lock()) 129 | { 130 | return slots->invoke...>(args...); 131 | } 132 | }; 133 | } 134 | 135 | private: 136 | detail::signal_impl_ptr m_slots; 137 | }; 138 | 139 | } // namespace is::signals 140 | 141 | namespace std 142 | { 143 | 144 | // free swap function, findable by ADL 145 | template class Combiner> 146 | void swap( 147 | ::is::signals::signal& sig1, 148 | ::is::signals::signal& sig2) 149 | { 150 | sig1.swap(sig2); 151 | } 152 | 153 | } // namespace std 154 | -------------------------------------------------------------------------------- /libfastsignals/include/function_detail.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace is::signals::detail 9 | { 10 | /// Buffer for callable object in-place construction, 11 | /// helps to implement Small Buffer Optimization. 12 | static constexpr size_t inplace_buffer_size = (sizeof(int) == sizeof(void*) ? 8 : 6) * sizeof(void*); 13 | 14 | /// Structure that has size enough to keep type "T" including vtable. 15 | template 16 | struct type_container 17 | { 18 | T data; 19 | }; 20 | 21 | /// Type that has enough space to keep type "T" (including vtable) with suitable alignment. 22 | using function_buffer_t = std::aligned_storage_t; 23 | 24 | /// Constantly is true if callable fits function buffer, false otherwise. 25 | template 26 | inline constexpr bool fits_inplace_buffer = (sizeof(type_container) <= inplace_buffer_size); 27 | 28 | // clang-format off 29 | /// Constantly is true if callable fits function buffer and can be safely moved, false otherwise 30 | template 31 | inline constexpr bool can_use_inplace_buffer = 32 | fits_inplace_buffer && 33 | std::is_nothrow_move_constructible_v; 34 | // clang format on 35 | 36 | /// Type that is suitable to keep copy of callable object. 37 | /// - equal to pointer-to-function if Callable is pointer-to-function 38 | /// - otherwise removes const/volatile and references to allow copying callable. 39 | template 40 | using callable_copy_t = std::conditional_t>, 41 | Callable, 42 | std::remove_cv_t>>; 43 | 44 | class base_function_proxy 45 | { 46 | public: 47 | virtual ~base_function_proxy() = default; 48 | virtual base_function_proxy* clone(void* buffer) const = 0; 49 | virtual base_function_proxy* move(void* buffer) noexcept = 0; 50 | }; 51 | 52 | template 53 | class function_proxy; 54 | 55 | template 56 | class function_proxy : public base_function_proxy 57 | { 58 | public: 59 | virtual Return operator()(Arguments&&...) = 0; 60 | }; 61 | 62 | template 63 | class function_proxy_impl final : public function_proxy 64 | { 65 | public: 66 | // If you see this error, probably your function returns value and you're trying to 67 | // connect it to `signal`. Just remove return value from callback. 68 | static_assert(std::is_same_v, Return>, 69 | "cannot construct function<> class from callable object with different return type"); 70 | 71 | template 72 | explicit function_proxy_impl(FunctionObject&& function) 73 | : m_callable(std::forward(function)) 74 | { 75 | } 76 | 77 | Return operator()(Arguments&&... args) final 78 | { 79 | return m_callable(std::forward(args)...); 80 | } 81 | 82 | base_function_proxy* clone(void* buffer) const final 83 | { 84 | if constexpr (can_use_inplace_buffer) 85 | { 86 | return new (buffer) function_proxy_impl(*this); 87 | } 88 | else 89 | { 90 | (void)buffer; 91 | return new function_proxy_impl(*this); 92 | } 93 | } 94 | 95 | base_function_proxy* move(void* buffer) noexcept final 96 | { 97 | if constexpr (can_use_inplace_buffer) 98 | { 99 | base_function_proxy* moved = new (buffer) function_proxy_impl(std::move(*this)); 100 | this->~function_proxy_impl(); 101 | return moved; 102 | } 103 | else 104 | { 105 | (void)buffer; 106 | return this; 107 | } 108 | } 109 | 110 | private: 111 | callable_copy_t m_callable; 112 | }; 113 | 114 | template 115 | inline constexpr bool is_noexcept_packed_function_init = can_use_inplace_buffer>; 116 | 117 | class packed_function final 118 | { 119 | public: 120 | packed_function() = default; 121 | packed_function(packed_function&& other) noexcept; 122 | packed_function(const packed_function& other); 123 | packed_function& operator=(packed_function&& other) noexcept; 124 | packed_function& operator=(const packed_function& other); 125 | ~packed_function() noexcept; 126 | 127 | // Initializes packed function. 128 | // Cannot be called without reset(). 129 | template 130 | void init(Callable&& function) noexcept(is_noexcept_packed_function_init) 131 | { 132 | using proxy_t = function_proxy_impl; 133 | 134 | assert(m_proxy == nullptr); 135 | if constexpr (can_use_inplace_buffer) 136 | { 137 | m_proxy = new (&m_buffer) proxy_t{ std::forward(function) }; 138 | } 139 | else 140 | { 141 | m_proxy = new proxy_t{ std::forward(function) }; 142 | } 143 | } 144 | 145 | template 146 | function_proxy& get() const 147 | { 148 | return static_cast&>(unwrap()); 149 | } 150 | 151 | void reset() noexcept; 152 | 153 | private: 154 | base_function_proxy* move_proxy_from(packed_function&& other) noexcept; 155 | base_function_proxy* clone_proxy_from(const packed_function &other); 156 | base_function_proxy& unwrap() const; 157 | bool is_buffer_allocated() const noexcept; 158 | 159 | function_buffer_t m_buffer[1] = {}; 160 | base_function_proxy* m_proxy = nullptr; 161 | }; 162 | 163 | } // namespace is::signals::detail 164 | -------------------------------------------------------------------------------- /docs/migration-from-boost-signals2.md: -------------------------------------------------------------------------------- 1 | # Migration from Boost.Signals2 2 | 3 | This guide helps to migrate large codebase from Boost.Signals2 to `FastSignals` signals/slots library. It helps to solve known migration issues in the right way. 4 | 5 | During migrations, you will probably face with following things: 6 | 7 | * You code uses `boost::signals2::` namespace and `` header directly 8 | * You code uses third-party headers included implicitly by the `` header 9 | 10 | ## Reasons migrate from Boost.Signals2 to FastSignals 11 | 12 | FastSignals API mostly compatible with Boost.Signals2 - there are differences, and all differences has their reasons explained below. 13 | 14 | Comparing to Boost.Signals2, FastSignals has following pros: 15 | 16 | * FastSignals is not header-only - so binary code will be more compact 17 | * FastSignals implemented using C++17 with variadic templates, `constexpr if` and other modern metaprogramming techniques - so it compiles faster and, again, binary code will be more compact 18 | * FastSignals probably will faster than Boost.Signals2 for your codebase because with FastSignals you don't pay for things that you don't use, with one exception: you always pay for the multithreading support 19 | 20 | ## Step 1: Create header with aliases 21 | 22 | ## Step 2: Rebuild and fix compile errors 23 | 24 | ### 2.1 Add missing includes 25 | 26 | Boost.Signals2 is header-only library. It includes a lot of STL/Boost stuff while FastSignals does not: 27 | 28 | ```cpp 29 | #include 30 | // Also includes std::map, boost::variant, boost::optional, etc. 31 | 32 | // Compiled OK even without `#include `! 33 | std::map CreateMyMap(); 34 | ``` 35 | 36 | With FastSignals, you must include headers like `` manually. The following table shows which files should be included explicitly if you see compile erros after migration. 37 | 38 | | Class | Header | 39 | |--------------------|:--------------------------------------:| 40 | | std::map | `#include ` | 41 | | boost::variant | `#include ` | 42 | | boost::optional | `#include ` | 43 | | boost::scoped_ptr | `#include ` | 44 | | boost::noncopyable | `#include ` | 45 | | boost::bind | `#include ` | 46 | | boost::function | `#include ` | 47 | 48 | If you just want to compile you code, you can add following includes in you `signals.h` header: 49 | 50 | ```cpp 51 | // WARNING: [libfastsignals] we do not recommend to include following extra headers. 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | ``` 60 | 61 | ### 2.2 Remove redundant returns for void signals 62 | 63 | With Boost.Signals2, following code compiled without any warning: 64 | 65 | ```cpp 66 | boost::signals2::signal event; 67 | event.connect([] { 68 | return true; 69 | }); 70 | ``` 71 | 72 | With FastSignals, slot cannot return non-void value when for `signal`. You must fix your code: just remove returns from your slots or add lambdas to wrap slot and ignore it result. 73 | 74 | ### 2.3 Replace track() and track_foreign() with bind_weak_ptr() 75 | 76 | Boost.Signals2 [can track connected objects lifetype](https://www.boost.org/doc/libs/1_55_0/doc/html/signals2/tutorial.html#idp204830936) using `track(...)` and `track_foreign(...)` methods. In the following example `Entity` created with `make_shared`, and `Entity::get_print_slot()` creates slot function which tracks weak pointer to Entity: 77 | 78 | ```cpp 79 | #include 80 | #include 81 | #include 82 | 83 | using VoidSignal = boost::signals2::signal; 84 | using VoidSlot = VoidSignal::slot_type; 85 | 86 | struct Entity : std::enable_shared_from_this 87 | { 88 | int value = 42; 89 | 90 | VoidSlot get_print_slot() 91 | { 92 | // Here track() tracks object itself. 93 | return VoidSlot(std::bind(&Entity::print, this)).track_foreign(shared_from_this()); 94 | } 95 | 96 | void print() 97 | { 98 | std::cout << "print called, num = " << value << std::endl; 99 | } 100 | }; 101 | 102 | int main() 103 | { 104 | VoidSignal event; 105 | auto entity = std::make_shared(); 106 | event.connect(entity->get_print_slot()); 107 | 108 | // Here slot called - it prints `print called, num = 42` 109 | event(); 110 | entity = nullptr; 111 | 112 | // This call does nothing. 113 | event(); 114 | } 115 | ``` 116 | 117 | FastSignals uses another approach: `bind_weak` function: 118 | 119 | ```cpp 120 | #include "fastsignals/bind_weak.h" 121 | #include 122 | 123 | using VoidSignal = is::signals::signal; 124 | using VoidSlot = VoidSignal::slot_type; 125 | 126 | struct Entity : std::enable_shared_from_this 127 | { 128 | int value = 42; 129 | 130 | VoidSlot get_print_slot() 131 | { 132 | // Here is::signals::bind_weak() used instead of std::bind. 133 | return is::signals::bind_weak(&Entity::print, weak_from_this()); 134 | } 135 | 136 | void print() 137 | { 138 | std::cout << "print called, num = " << value << std::endl; 139 | } 140 | }; 141 | 142 | int main() 143 | { 144 | VoidSignal event; 145 | auto entity = std::make_shared(); 146 | event.connect(entity->get_print_slot()); 147 | 148 | // Here slot called - it prints `slot called, num = 42` 149 | event(); 150 | entity = nullptr; 151 | 152 | // Here nothing happens - no exception, no slot call. 153 | event(); 154 | } 155 | ``` 156 | 157 | ### FastSignals Differences in Result Combiners 158 | 159 | ## Step 3: Run Tests 160 | 161 | Run all automated tests that you have (unit tests, integration tests, system tests, stress tests, benchmarks, UI tests). 162 | 163 | Probably you will see no errors. If you see any, please report an issue. 164 | -------------------------------------------------------------------------------- /libfastsignals/src/connection.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/connection.h" 2 | 3 | namespace is::signals 4 | { 5 | namespace 6 | { 7 | 8 | auto get_advanced_connection_impl(const advanced_connection& connection) noexcept 9 | { 10 | struct advanced_connection_impl_getter : private advanced_connection 11 | { 12 | advanced_connection_impl_getter(const advanced_connection& connection) noexcept 13 | : advanced_connection(connection) 14 | { 15 | } 16 | using advanced_connection::m_impl; 17 | }; 18 | return advanced_connection_impl_getter(connection).m_impl; 19 | } 20 | 21 | } // namespace 22 | 23 | connection::connection(connection&& other) noexcept 24 | : m_storage(other.m_storage) 25 | , m_id(other.m_id) 26 | { 27 | other.m_storage.reset(); 28 | other.m_id = 0; 29 | } 30 | 31 | connection::connection(detail::signal_impl_weak_ptr storage, uint64_t id) noexcept 32 | : m_storage(std::move(storage)) 33 | , m_id(id) 34 | { 35 | } 36 | 37 | connection::connection() noexcept = default; 38 | 39 | connection::connection(const connection& other) noexcept = default; 40 | 41 | connection& connection::operator=(connection&& other) noexcept 42 | { 43 | m_storage = other.m_storage; 44 | m_id = other.m_id; 45 | other.m_storage.reset(); 46 | other.m_id = 0; 47 | return *this; 48 | } 49 | 50 | connection& connection::operator=(const connection& other) noexcept = default; 51 | 52 | bool connection::connected() const noexcept 53 | { 54 | return (m_id != 0); 55 | } 56 | 57 | void connection::disconnect() noexcept 58 | { 59 | if (auto storage = m_storage.lock()) 60 | { 61 | storage->remove(m_id); 62 | m_storage.reset(); 63 | } 64 | m_id = 0; 65 | } 66 | 67 | scoped_connection::scoped_connection(connection&& conn) noexcept 68 | : connection(std::move(conn)) 69 | { 70 | } 71 | 72 | scoped_connection::scoped_connection(const connection& conn) noexcept 73 | : connection(conn) 74 | { 75 | } 76 | 77 | scoped_connection::scoped_connection() noexcept = default; 78 | 79 | scoped_connection::scoped_connection(scoped_connection&& other) noexcept = default; 80 | 81 | scoped_connection& scoped_connection::operator=(scoped_connection&& other) noexcept 82 | { 83 | disconnect(); 84 | static_cast(*this) = std::move(other); 85 | return *this; 86 | } 87 | 88 | scoped_connection::~scoped_connection() 89 | { 90 | disconnect(); 91 | } 92 | 93 | connection scoped_connection::release() noexcept 94 | { 95 | connection conn = std::move(static_cast(*this)); 96 | return conn; 97 | } 98 | 99 | bool advanced_connection::advanced_connection_impl::is_blocked() const noexcept 100 | { 101 | return m_blockCounter.load(std::memory_order_acquire) != 0; 102 | } 103 | 104 | void advanced_connection::advanced_connection_impl::block() noexcept 105 | { 106 | ++m_blockCounter; 107 | } 108 | 109 | void advanced_connection::advanced_connection_impl::unblock() noexcept 110 | { 111 | --m_blockCounter; 112 | } 113 | 114 | advanced_connection::advanced_connection() noexcept = default; 115 | 116 | advanced_connection::advanced_connection(connection&& conn, impl_ptr&& impl) noexcept 117 | : connection(std::move(conn)) 118 | , m_impl(std::move(impl)) 119 | { 120 | } 121 | 122 | advanced_connection::advanced_connection(const advanced_connection&) noexcept = default; 123 | 124 | advanced_connection::advanced_connection(advanced_connection&& other) noexcept = default; 125 | 126 | advanced_connection& advanced_connection::operator=(const advanced_connection&) noexcept = default; 127 | 128 | advanced_connection& advanced_connection::operator=(advanced_connection&& other) noexcept = default; 129 | 130 | shared_connection_block::shared_connection_block(const advanced_connection& connection, bool initially_blocked) noexcept 131 | : m_connection(get_advanced_connection_impl(connection)) 132 | { 133 | if (initially_blocked) 134 | { 135 | block(); 136 | } 137 | } 138 | 139 | shared_connection_block::shared_connection_block(const shared_connection_block& other) noexcept 140 | : m_connection(other.m_connection) 141 | , m_blocked(other.m_blocked.load(std::memory_order_acquire)) 142 | { 143 | increment_if_blocked(); 144 | } 145 | 146 | shared_connection_block::shared_connection_block(shared_connection_block&& other) noexcept 147 | : m_connection(other.m_connection) 148 | , m_blocked(other.m_blocked.load(std::memory_order_acquire)) 149 | { 150 | other.m_connection.reset(); 151 | other.m_blocked.store(false, std::memory_order_release); 152 | } 153 | 154 | shared_connection_block& shared_connection_block::operator=(const shared_connection_block& other) noexcept 155 | { 156 | if (&other != this) 157 | { 158 | unblock(); 159 | m_connection = other.m_connection; 160 | m_blocked = other.m_blocked.load(std::memory_order_acquire); 161 | increment_if_blocked(); 162 | } 163 | return *this; 164 | } 165 | 166 | shared_connection_block& shared_connection_block::operator=(shared_connection_block&& other) noexcept 167 | { 168 | if (&other != this) 169 | { 170 | unblock(); 171 | m_connection = other.m_connection; 172 | m_blocked = other.m_blocked.load(std::memory_order_acquire); 173 | other.m_connection.reset(); 174 | other.m_blocked.store(false, std::memory_order_release); 175 | } 176 | return *this; 177 | } 178 | 179 | shared_connection_block::~shared_connection_block() 180 | { 181 | unblock(); 182 | } 183 | 184 | void shared_connection_block::block() noexcept 185 | { 186 | bool blocked = false; 187 | if (m_blocked.compare_exchange_strong(blocked, true, std::memory_order_acq_rel, std::memory_order_relaxed)) 188 | { 189 | if (auto connection = m_connection.lock()) 190 | { 191 | connection->block(); 192 | } 193 | } 194 | } 195 | 196 | void shared_connection_block::unblock() noexcept 197 | { 198 | bool blocked = true; 199 | if (m_blocked.compare_exchange_strong(blocked, false, std::memory_order_acq_rel, std::memory_order_relaxed)) 200 | { 201 | if (auto connection = m_connection.lock()) 202 | { 203 | connection->unblock(); 204 | } 205 | } 206 | } 207 | 208 | bool shared_connection_block::blocking() const noexcept 209 | { 210 | return m_blocked; 211 | } 212 | 213 | void shared_connection_block::increment_if_blocked() const noexcept 214 | { 215 | if (m_blocked) 216 | { 217 | if (auto connection = m_connection.lock()) 218 | { 219 | connection->block(); 220 | } 221 | } 222 | } 223 | 224 | advanced_scoped_connection::advanced_scoped_connection() noexcept = default; 225 | 226 | advanced_scoped_connection::advanced_scoped_connection(const advanced_connection& conn) noexcept 227 | : advanced_connection(conn) 228 | { 229 | } 230 | 231 | advanced_scoped_connection::advanced_scoped_connection(advanced_connection&& conn) noexcept 232 | : advanced_connection(std::move(conn)) 233 | { 234 | } 235 | 236 | advanced_scoped_connection::advanced_scoped_connection(advanced_scoped_connection&& other) noexcept = default; 237 | 238 | advanced_scoped_connection& advanced_scoped_connection::operator=(advanced_scoped_connection&& other) noexcept = default; 239 | 240 | advanced_scoped_connection::~advanced_scoped_connection() 241 | { 242 | disconnect(); 243 | } 244 | 245 | advanced_connection advanced_scoped_connection::release() noexcept 246 | { 247 | advanced_connection conn = std::move(static_cast(*this)); 248 | return conn; 249 | } 250 | 251 | } // namespace is::signals 252 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | ## Ignore Visual Studio temporary files, build results, and 35 | ## files generated by popular Visual Studio add-ons. 36 | ## 37 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.userosscache 43 | *.sln.docstates 44 | 45 | # User-specific files (MonoDevelop/Xamarin Studio) 46 | *.userprefs 47 | 48 | # Build results 49 | [Dd]ebug/ 50 | [Dd]ebugPublic/ 51 | [Rr]elease/ 52 | [Rr]eleases/ 53 | x64/ 54 | x86/ 55 | bld/ 56 | [Bb]in/ 57 | [Oo]bj/ 58 | [Ll]og/ 59 | 60 | # Visual Studio 2015/2017 cache/options directory 61 | .vs/ 62 | # Uncomment if you have tasks that create the project's static files in wwwroot 63 | #wwwroot/ 64 | 65 | # Visual Studio 2017 auto generated files 66 | Generated\ Files/ 67 | 68 | # MSTest test Results 69 | [Tt]est[Rr]esult*/ 70 | [Bb]uild[Ll]og.* 71 | 72 | # NUNIT 73 | *.VisualState.xml 74 | TestResult.xml 75 | 76 | # Build Results of an ATL Project 77 | [Dd]ebugPS/ 78 | [Rr]eleasePS/ 79 | dlldata.c 80 | 81 | # Benchmark Results 82 | BenchmarkDotNet.Artifacts/ 83 | 84 | # .NET Core 85 | project.lock.json 86 | project.fragment.lock.json 87 | artifacts/ 88 | 89 | # StyleCop 90 | StyleCopReport.xml 91 | 92 | # Files built by Visual Studio 93 | *_i.c 94 | *_p.c 95 | *_i.h 96 | *.ilk 97 | *.meta 98 | *.obj 99 | *.iobj 100 | *.pch 101 | *.pdb 102 | *.ipdb 103 | *.pgc 104 | *.pgd 105 | *.rsp 106 | *.sbr 107 | *.tlb 108 | *.tli 109 | *.tlh 110 | *.tmp 111 | *.tmp_proj 112 | *.log 113 | *.vspscc 114 | *.vssscc 115 | .builds 116 | *.pidb 117 | *.svclog 118 | *.scc 119 | 120 | # Chutzpah Test files 121 | _Chutzpah* 122 | 123 | # Visual C++ cache files 124 | ipch/ 125 | *.aps 126 | *.ncb 127 | *.opendb 128 | *.opensdf 129 | *.sdf 130 | *.cachefile 131 | *.VC.db 132 | *.VC.VC.opendb 133 | 134 | # Visual Studio profiler 135 | *.psess 136 | *.vsp 137 | *.vspx 138 | *.sap 139 | 140 | # Visual Studio Trace Files 141 | *.e2e 142 | 143 | # TFS 2012 Local Workspace 144 | $tf/ 145 | 146 | # Guidance Automation Toolkit 147 | *.gpState 148 | 149 | # ReSharper is a .NET coding add-in 150 | _ReSharper*/ 151 | *.[Rr]e[Ss]harper 152 | *.DotSettings.user 153 | 154 | # JustCode is a .NET coding add-in 155 | .JustCode 156 | 157 | # TeamCity is a build add-in 158 | _TeamCity* 159 | 160 | # DotCover is a Code Coverage Tool 161 | *.dotCover 162 | 163 | # AxoCover is a Code Coverage Tool 164 | .axoCover/* 165 | !.axoCover/settings.json 166 | 167 | # Visual Studio code coverage results 168 | *.coverage 169 | *.coveragexml 170 | 171 | # NCrunch 172 | _NCrunch_* 173 | .*crunch*.local.xml 174 | nCrunchTemp_* 175 | 176 | # MightyMoose 177 | *.mm.* 178 | AutoTest.Net/ 179 | 180 | # Web workbench (sass) 181 | .sass-cache/ 182 | 183 | # Installshield output folder 184 | [Ee]xpress/ 185 | 186 | # DocProject is a documentation generator add-in 187 | DocProject/buildhelp/ 188 | DocProject/Help/*.HxT 189 | DocProject/Help/*.HxC 190 | DocProject/Help/*.hhc 191 | DocProject/Help/*.hhk 192 | DocProject/Help/*.hhp 193 | DocProject/Help/Html2 194 | DocProject/Help/html 195 | 196 | # Click-Once directory 197 | publish/ 198 | 199 | # Publish Web Output 200 | *.[Pp]ublish.xml 201 | *.azurePubxml 202 | # Note: Comment the next line if you want to checkin your web deploy settings, 203 | # but database connection strings (with potential passwords) will be unencrypted 204 | *.pubxml 205 | *.publishproj 206 | 207 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 208 | # checkin your Azure Web App publish settings, but sensitive information contained 209 | # in these scripts will be unencrypted 210 | PublishScripts/ 211 | 212 | # NuGet Packages 213 | *.nupkg 214 | # The packages folder can be ignored because of Package Restore 215 | **/[Pp]ackages/* 216 | # except build/, which is used as an MSBuild target. 217 | !**/[Pp]ackages/build/ 218 | # Uncomment if necessary however generally it will be regenerated when needed 219 | #!**/[Pp]ackages/repositories.config 220 | # NuGet v3's project.json files produces more ignorable files 221 | *.nuget.props 222 | *.nuget.targets 223 | 224 | # Microsoft Azure Build Output 225 | csx/ 226 | *.build.csdef 227 | 228 | # Microsoft Azure Emulator 229 | ecf/ 230 | rcf/ 231 | 232 | # Windows Store app package directories and files 233 | AppPackages/ 234 | BundleArtifacts/ 235 | Package.StoreAssociation.xml 236 | _pkginfo.txt 237 | *.appx 238 | 239 | # Visual Studio cache files 240 | # files ending in .cache can be ignored 241 | *.[Cc]ache 242 | # but keep track of directories ending in .cache 243 | !*.[Cc]ache/ 244 | 245 | # Others 246 | ClientBin/ 247 | ~$* 248 | *~ 249 | *.dbmdl 250 | *.dbproj.schemaview 251 | *.jfm 252 | *.pfx 253 | *.publishsettings 254 | orleans.codegen.cs 255 | 256 | # Including strong name files can present a security risk 257 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 258 | #*.snk 259 | 260 | # Since there are multiple workflows, uncomment next line to ignore bower_components 261 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 262 | #bower_components/ 263 | 264 | # RIA/Silverlight projects 265 | Generated_Code/ 266 | 267 | # Backup & report files from converting an old project file 268 | # to a newer Visual Studio version. Backup files are not needed, 269 | # because we have git ;-) 270 | _UpgradeReport_Files/ 271 | Backup*/ 272 | UpgradeLog*.XML 273 | UpgradeLog*.htm 274 | ServiceFabricBackup/ 275 | *.rptproj.bak 276 | 277 | # SQL Server files 278 | *.mdf 279 | *.ldf 280 | *.ndf 281 | 282 | # Business Intelligence projects 283 | *.rdl.data 284 | *.bim.layout 285 | *.bim_*.settings 286 | *.rptproj.rsuser 287 | 288 | # Microsoft Fakes 289 | FakesAssemblies/ 290 | 291 | # GhostDoc plugin setting file 292 | *.GhostDoc.xml 293 | 294 | # Node.js Tools for Visual Studio 295 | .ntvs_analysis.dat 296 | node_modules/ 297 | 298 | # Visual Studio 6 build log 299 | *.plg 300 | 301 | # Visual Studio 6 workspace options file 302 | *.opt 303 | 304 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 305 | *.vbw 306 | 307 | # Visual Studio LightSwitch build output 308 | **/*.HTMLClient/GeneratedArtifacts 309 | **/*.DesktopClient/GeneratedArtifacts 310 | **/*.DesktopClient/ModelManifest.xml 311 | **/*.Server/GeneratedArtifacts 312 | **/*.Server/ModelManifest.xml 313 | _Pvt_Extensions 314 | 315 | # Paket dependency manager 316 | .paket/paket.exe 317 | paket-files/ 318 | 319 | # FAKE - F# Make 320 | .fake/ 321 | 322 | # JetBrains Rider 323 | .idea/ 324 | *.sln.iml 325 | 326 | # CodeRush 327 | .cr/ 328 | 329 | # Python Tools for Visual Studio (PTVS) 330 | __pycache__/ 331 | *.pyc 332 | 333 | # Cake - Uncomment if you are using it 334 | # tools/** 335 | # !tools/packages.config 336 | 337 | # Tabs Studio 338 | *.tss 339 | 340 | # Telerik's JustMock configuration file 341 | *.jmconfig 342 | 343 | # BizTalk build output 344 | *.btp.cs 345 | *.btm.cs 346 | *.odx.cs 347 | *.xsd.cs 348 | 349 | # OpenCover UI analysis results 350 | OpenCover/ 351 | 352 | # Azure Stream Analytics local run output 353 | ASALocalRun/ 354 | 355 | # MSBuild Binary and Structured Log 356 | *.binlog 357 | 358 | # NVidia Nsight GPU debugger configuration file 359 | *.nvuser 360 | 361 | # MFractors (Xamarin productivity tool) working folder 362 | .mfractor/ 363 | 364 | # ------------ 365 | # Custom Rules 366 | 367 | build/ 368 | -------------------------------------------------------------------------------- /tests/libfastsignals_stress_tests/libfastsignals_stress_tests.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {751DC150-1907-4D9F-8566-AA4E24FDFA64} 24 | Win32Proj 25 | libfastsignalsstresstests 26 | 27 | 28 | 29 | Application 30 | true 31 | v141 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v141 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v141 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v141 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | false 78 | $(SolutionDir);$(SolutionDir)tests;$(IncludePath) 79 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 80 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 81 | 82 | 83 | true 84 | $(SolutionDir);$(SolutionDir)tests;$(IncludePath) 85 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 86 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 87 | 88 | 89 | true 90 | $(SolutionDir);$(SolutionDir)tests;$(IncludePath) 91 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 92 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 93 | 94 | 95 | false 96 | $(SolutionDir);$(SolutionDir)tests;$(IncludePath) 97 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 98 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 99 | 100 | 101 | 102 | NotUsing 103 | Level3 104 | MaxSpeed 105 | true 106 | true 107 | true 108 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 109 | true 110 | stdcpp17 111 | 112 | 113 | Console 114 | true 115 | true 116 | true 117 | 118 | 119 | 120 | 121 | NotUsing 122 | Level3 123 | Disabled 124 | true 125 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 126 | true 127 | stdcpp17 128 | 129 | 130 | Console 131 | true 132 | 133 | 134 | 135 | 136 | NotUsing 137 | Level3 138 | Disabled 139 | true 140 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 141 | true 142 | stdcpp17 143 | 144 | 145 | Console 146 | true 147 | 148 | 149 | 150 | 151 | NotUsing 152 | Level3 153 | MaxSpeed 154 | true 155 | true 156 | true 157 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 158 | true 159 | stdcpp17 160 | 161 | 162 | Console 163 | true 164 | true 165 | true 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | {32bd918f-edbc-4057-a033-10dc361da4a0} 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /tests/libfastsignals_unit_tests/libfastsignals_unit_tests.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {BAC23A51-8DC1-4589-940F-9923D8E12718} 24 | Win32Proj 25 | libfastsignals_unit_tests 26 | 27 | 28 | 29 | Application 30 | true 31 | v141 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v141 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v141 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v141 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | true 78 | $(SolutionDir);$(SolutionDir)tests;$(IncludePath) 79 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 80 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 81 | 82 | 83 | true 84 | $(SolutionDir);$(SolutionDir)tests;$(IncludePath) 85 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 86 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 87 | 88 | 89 | false 90 | $(SolutionDir);$(SolutionDir)tests;$(IncludePath) 91 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 92 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 93 | 94 | 95 | false 96 | $(SolutionDir);$(SolutionDir)tests;$(IncludePath) 97 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 98 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 99 | 100 | 101 | 102 | NotUsing 103 | Disabled 104 | true 105 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 106 | true 107 | stdcpp17 108 | 109 | 110 | Console 111 | true 112 | 113 | 114 | $(TargetPath) 115 | 116 | 117 | 118 | 119 | NotUsing 120 | Disabled 121 | true 122 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 123 | true 124 | stdcpp17 125 | 126 | 127 | Console 128 | true 129 | 130 | 131 | $(TargetPath) 132 | 133 | 134 | 135 | 136 | NotUsing 137 | MaxSpeed 138 | true 139 | true 140 | true 141 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 142 | true 143 | stdcpp17 144 | 145 | 146 | Console 147 | true 148 | true 149 | true 150 | 151 | 152 | $(TargetPath) 153 | 154 | 155 | 156 | 157 | NotUsing 158 | MaxSpeed 159 | true 160 | true 161 | true 162 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 163 | true 164 | stdcpp17 165 | 166 | 167 | Console 168 | true 169 | true 170 | true 171 | 172 | 173 | $(TargetPath) 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | {32bd918f-edbc-4057-a033-10dc361da4a0} 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /libfastsignals/libfastsignals.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {32BD918F-EDBC-4057-A033-10DC361DA4A0} 24 | Win32Proj 25 | FastSignals 26 | 27 | 28 | 29 | StaticLibrary 30 | true 31 | v141 32 | Unicode 33 | 34 | 35 | StaticLibrary 36 | false 37 | v141 38 | true 39 | Unicode 40 | 41 | 42 | StaticLibrary 43 | true 44 | v141 45 | Unicode 46 | 47 | 48 | StaticLibrary 49 | false 50 | v141 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | true 78 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 79 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 80 | $(ProjectName)$(DebugSuffixOpt)-$(PlatformToolset)$(PlatformSuffix) 81 | true 82 | 83 | 84 | true 85 | $(SolutionDir)3rdparty;$(IncludePath) 86 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 87 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 88 | $(ProjectName)$(DebugSuffixOpt)-$(PlatformToolset)$(PlatformSuffix) 89 | NativeRecommendedRules.ruleset 90 | true 91 | 92 | 93 | false 94 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 95 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 96 | $(ProjectName)$(DebugSuffixOpt)-$(PlatformToolset)$(PlatformSuffix) 97 | true 98 | 99 | 100 | false 101 | $(SolutionDir)3rdparty;$(IncludePath) 102 | $(SolutionDir)build\bin\$(ProjectName)\$(Platform)\$(Configuration)\ 103 | $(SolutionDir)build\tmp\$(ProjectName)\$(Platform)\$(Configuration)\ 104 | $(ProjectName)$(DebugSuffixOpt)-$(PlatformToolset)$(PlatformSuffix) 105 | NativeRecommendedRules.ruleset 106 | true 107 | 108 | 109 | 110 | NotUsing 111 | Disabled 112 | true 113 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 114 | true 115 | stdcpp17 116 | true 117 | 26495;26439;%(DisableSpecificWarnings) 118 | 119 | 120 | Console 121 | true 122 | 123 | 124 | 125 | 126 | NotUsing 127 | Disabled 128 | true 129 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 130 | true 131 | stdcpp17 132 | true 133 | 26495;26439;%(DisableSpecificWarnings) 134 | 135 | 136 | Console 137 | true 138 | 139 | 140 | 141 | 142 | NotUsing 143 | MaxSpeed 144 | true 145 | true 146 | true 147 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 148 | true 149 | stdcpp17 150 | true 151 | 26495;26439;%(DisableSpecificWarnings) 152 | 153 | 154 | Console 155 | true 156 | true 157 | true 158 | 159 | 160 | 161 | 162 | NotUsing 163 | MaxSpeed 164 | true 165 | true 166 | true 167 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 168 | true 169 | stdcpp17 170 | true 171 | 26495;26439;%(DisableSpecificWarnings) 172 | 173 | 174 | Console 175 | true 176 | true 177 | true 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /tests/libfastsignals_unit_tests/Function_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "catch2/catch.hpp" 2 | #include "libfastsignals/include/function.h" 3 | #include 4 | 5 | using namespace is::signals; 6 | 7 | namespace 8 | { 9 | int Abs(int x) 10 | { 11 | return x >= 0 ? x : -x; 12 | } 13 | 14 | int Sum(int a, int b) 15 | { 16 | return a + b; 17 | } 18 | 19 | void InplaceAbs(int& x) 20 | { 21 | x = Abs(x); 22 | } 23 | 24 | std::string GetStringHello() 25 | { 26 | return "hello"; 27 | } 28 | 29 | class AbsFunctor 30 | { 31 | public: 32 | int operator()(int x) const 33 | { 34 | return Abs(x); 35 | } 36 | }; 37 | 38 | class SumFunctor 39 | { 40 | public: 41 | int operator()(int a, int b) const 42 | { 43 | return Sum(a, b); 44 | } 45 | }; 46 | 47 | class InplaceAbsFunctor 48 | { 49 | public: 50 | void operator()(int& x) /* non-const */ 51 | { 52 | if (m_calledOnce) 53 | { 54 | abort(); 55 | } 56 | m_calledOnce = true; 57 | InplaceAbs(x); 58 | } 59 | 60 | private: 61 | bool m_calledOnce = false; 62 | }; 63 | 64 | class GetStringFunctor 65 | { 66 | public: 67 | explicit GetStringFunctor(const std::string& value) 68 | : m_value(value) 69 | { 70 | } 71 | 72 | std::string operator()() /* non-const */ 73 | { 74 | if (m_calledOnce) 75 | { 76 | abort(); 77 | } 78 | m_calledOnce = true; 79 | return m_value; 80 | } 81 | 82 | private: 83 | bool m_calledOnce = false; 84 | std::string m_value; 85 | }; 86 | } // namespace 87 | 88 | TEST_CASE("Can use free function with 1 argument", "[function]") 89 | { 90 | function fn = Abs; 91 | REQUIRE(fn(10) == 10); 92 | REQUIRE(fn(-10) == 10); 93 | REQUIRE(fn(0) == 0); 94 | } 95 | 96 | TEST_CASE("Can use free function with 2 arguments", "[function]") 97 | { 98 | function fn = Sum; 99 | REQUIRE(fn(10, 5) == 15); 100 | REQUIRE(fn(-10, 0) == -10); 101 | } 102 | 103 | TEST_CASE("Can use free function without arguments", "[function]") 104 | { 105 | function fn = GetStringHello; 106 | REQUIRE(fn() == "hello"); 107 | } 108 | 109 | TEST_CASE("Can use free function without return value", "[function]") 110 | { 111 | function fn = InplaceAbs; 112 | int a = -10; 113 | fn(a); 114 | REQUIRE(a == 10); 115 | } 116 | 117 | TEST_CASE("Can use lambda with 1 argument", "[function]") 118 | { 119 | function fn = [](int value) { 120 | return Abs(value); 121 | }; 122 | REQUIRE(fn(10) == 10); 123 | REQUIRE(fn(-10) == 10); 124 | REQUIRE(fn(0) == 0); 125 | } 126 | 127 | TEST_CASE("Can use lambda with 2 arguments", "[function]") 128 | { 129 | function fn = [](auto&& a, auto&& b) { 130 | return Sum(a, b); 131 | }; 132 | REQUIRE(fn(10, 5) == 15); 133 | REQUIRE(fn(-10, 0) == -10); 134 | } 135 | 136 | TEST_CASE("Can use lambda without arguments", "[function]") 137 | { 138 | function fn = [] { 139 | return GetStringHello(); 140 | }; 141 | REQUIRE(fn() == "hello"); 142 | } 143 | 144 | TEST_CASE("Can use lambda without return value", "[function]") 145 | { 146 | bool calledOnce = false; 147 | function fn = [calledOnce](auto& value) mutable { 148 | if (calledOnce) 149 | { 150 | abort(); 151 | } 152 | calledOnce = true; 153 | InplaceAbs(value); 154 | }; 155 | int a = -10; 156 | fn(a); 157 | REQUIRE(a == 10); 158 | } 159 | 160 | TEST_CASE("Can use functor with 1 argument", "[function]") 161 | { 162 | function fn = AbsFunctor(); 163 | REQUIRE(fn(10) == 10); 164 | REQUIRE(fn(-10) == 10); 165 | REQUIRE(fn(0) == 0); 166 | } 167 | 168 | TEST_CASE("Can use functor with 2 arguments", "[function]") 169 | { 170 | function fn = SumFunctor(); 171 | REQUIRE(fn(10, 5) == 15); 172 | REQUIRE(fn(-10, 0) == -10); 173 | } 174 | 175 | TEST_CASE("Can use functor without arguments", "[function]") 176 | { 177 | function fn = GetStringFunctor("hello"); 178 | REQUIRE(fn() == "hello"); 179 | } 180 | 181 | TEST_CASE("Can use functor without return value", "[function]") 182 | { 183 | function fn = InplaceAbsFunctor(); 184 | int a = -10; 185 | fn(a); 186 | REQUIRE(a == 10); 187 | } 188 | 189 | TEST_CASE("Can construct function with cons std::function<>&", "[function]") 190 | { 191 | using BoolCallback = std::function; 192 | bool value = false; 193 | const BoolCallback& cb = [&value](bool succeed) { 194 | value = succeed; 195 | }; 196 | 197 | function fn = cb; 198 | fn(true); 199 | REQUIRE(value == true); 200 | fn(false); 201 | REQUIRE(value == false); 202 | fn(true); 203 | REQUIRE(value == true); 204 | } 205 | 206 | TEST_CASE("Can copy function", "[function]") 207 | { 208 | unsigned calledCount = 0; 209 | bool value = false; 210 | function callback = [&](bool gotValue) { 211 | ++calledCount; 212 | value = gotValue; 213 | }; 214 | auto callback2 = callback; 215 | REQUIRE(calledCount == 0); 216 | CHECK(!value); 217 | callback(true); 218 | REQUIRE(calledCount == 1); 219 | CHECK(value); 220 | callback2(false); 221 | REQUIRE(calledCount == 2); 222 | CHECK(!value); 223 | } 224 | 225 | TEST_CASE("Can move function", "[function]") 226 | { 227 | bool called = false; 228 | function callback = [&] { 229 | called = true; 230 | }; 231 | auto callback2(std::move(callback)); 232 | REQUIRE_THROWS(callback()); 233 | REQUIRE(!called); 234 | callback2(); 235 | REQUIRE(called); 236 | } 237 | 238 | TEST_CASE("Works when copying self", "[function]") 239 | { 240 | bool called = false; 241 | function callback = [&] { 242 | called = true; 243 | }; 244 | callback = callback; 245 | callback(); 246 | REQUIRE(called); 247 | } 248 | 249 | TEST_CASE("Can release packed function", "[function]") 250 | { 251 | function iota = [v = 0]() mutable { 252 | return v++; 253 | }; 254 | REQUIRE(iota() == 0); 255 | 256 | auto packedFn = std::move(iota).release(); 257 | REQUIRE_THROWS_AS(iota(), std::bad_function_call); 258 | 259 | auto&& proxy = packedFn.get(); 260 | REQUIRE(proxy() == 1); 261 | REQUIRE(proxy() == 2); 262 | } 263 | 264 | TEST_CASE("Function copy has its own packed function", "[function]") 265 | { 266 | function iota = [v = 0]() mutable { 267 | return v++; 268 | }; 269 | 270 | REQUIRE(iota() == 0); 271 | 272 | auto iotaCopy(iota); 273 | 274 | REQUIRE(iota() == 1); 275 | REQUIRE(iota() == 2); 276 | 277 | REQUIRE(iotaCopy() == 1); 278 | REQUIRE(iotaCopy() == 2); 279 | } 280 | 281 | TEST_CASE("can work with callables that have vtable", "[function]") 282 | { 283 | class Base 284 | { 285 | }; 286 | 287 | class Interface : public Base 288 | { 289 | public: 290 | virtual ~Interface() = default; 291 | virtual void operator()() const = 0; 292 | }; 293 | class Class : public Interface 294 | { 295 | public: 296 | Class(bool* destructorCalled) 297 | : m_destructorCalled(destructorCalled) 298 | { 299 | } 300 | 301 | ~Class() 302 | { 303 | *m_destructorCalled = true; 304 | } 305 | 306 | void operator()() const override 307 | { 308 | } 309 | 310 | bool* m_destructorCalled = nullptr; 311 | }; 312 | bool destructorCalled = false; 313 | { 314 | function f = Class(&destructorCalled); 315 | f(); 316 | auto packed = f.release(); 317 | destructorCalled = false; 318 | } 319 | CHECK(destructorCalled); 320 | } 321 | 322 | TEST_CASE("can work with callables with virtual inheritance", "[function]") 323 | { 324 | struct A 325 | { 326 | void operator()() const 327 | { 328 | m_called = true; 329 | } 330 | 331 | ~A() 332 | { 333 | *m_destructorCalled = true; 334 | } 335 | 336 | mutable bool m_called = false; 337 | bool* m_destructorCalled = nullptr; 338 | }; 339 | struct B : public virtual A 340 | { 341 | }; 342 | struct C : public virtual A 343 | { 344 | }; 345 | struct D : virtual public B 346 | , virtual public C 347 | { 348 | D(bool* destructorCalled) 349 | { 350 | m_destructorCalled = destructorCalled; 351 | } 352 | 353 | using A::operator(); 354 | }; 355 | bool destructorCalled = false; 356 | { 357 | function f = D(&destructorCalled); 358 | f(); 359 | auto packed = f.release(); 360 | destructorCalled = false; 361 | } 362 | CHECK(destructorCalled); 363 | } 364 | 365 | TEST_CASE("uses copy constructor if callable's move constructor throws", "[function]") 366 | { 367 | struct Callable 368 | { 369 | Callable() = default; 370 | Callable(Callable&&) 371 | { 372 | throw std::runtime_error("throw"); 373 | } 374 | Callable(const Callable& other) = default; 375 | void operator()() const 376 | { 377 | } 378 | }; 379 | Callable c; 380 | function f(c); 381 | auto f2 = std::move(f); 382 | f2(); 383 | CHECK_THROWS(f()); 384 | } 385 | 386 | TEST_CASE("uses move constructor if it is noexcept", "[function]") 387 | { 388 | struct Callable 389 | { 390 | Callable() = default; 391 | Callable(Callable&& other) noexcept = default; 392 | Callable(const Callable&) 393 | { 394 | throw std::runtime_error("throw"); 395 | } 396 | void operator()() const 397 | { 398 | } 399 | }; 400 | Callable c; 401 | function f(std::move(c)); 402 | auto f2 = std::move(f); 403 | f2(); 404 | CHECK_THROWS(f()); 405 | } 406 | 407 | TEST_CASE("can copy and move empty function", "[function]") 408 | { 409 | function f; 410 | auto f2 = f; 411 | auto f3 = std::move(f); 412 | } 413 | 414 | TEST_CASE("properly copies callable on assignment", "[function]") 415 | { 416 | struct Callable 417 | { 418 | Callable(int& aliveCounter) 419 | : m_aliveCounter(&aliveCounter) 420 | { 421 | ++*m_aliveCounter; 422 | } 423 | Callable(const Callable& other) 424 | : m_aliveCounter(other.m_aliveCounter) 425 | { 426 | if (m_aliveCounter) 427 | { 428 | ++*m_aliveCounter; 429 | } 430 | } 431 | Callable(Callable&& other) noexcept 432 | : m_aliveCounter(other.m_aliveCounter) 433 | { 434 | other.m_aliveCounter = nullptr; 435 | } 436 | ~Callable() 437 | { 438 | if (m_aliveCounter) 439 | { 440 | --*m_aliveCounter; 441 | } 442 | } 443 | void operator()() const 444 | { 445 | } 446 | 447 | int* m_aliveCounter = nullptr; 448 | }; 449 | int aliveCounter1 = 0; 450 | int aliveCounter2 = 0; 451 | function f = Callable(aliveCounter1); 452 | function f2 = Callable(aliveCounter2); 453 | CHECK(aliveCounter1 == 1); 454 | CHECK(aliveCounter2 == 1); 455 | f = f2; 456 | CHECK(aliveCounter1 == 0); 457 | CHECK(aliveCounter2 == 2); 458 | f = function(); 459 | f2 = function(); 460 | CHECK(aliveCounter1 == 0); 461 | CHECK(aliveCounter2 == 0); 462 | } 463 | 464 | TEST_CASE("copy assignment operator provides strong exception safety", "[function]") 465 | { 466 | struct State 467 | { 468 | int callCount = 0; 469 | bool throwOnCopy = false; 470 | }; 471 | struct Callable 472 | { 473 | Callable(State& state) 474 | : state(&state) 475 | { 476 | } 477 | void operator()() 478 | { 479 | ++state->callCount; 480 | } 481 | Callable(const Callable& other) 482 | : state(other.state) 483 | { 484 | if (state->throwOnCopy) 485 | { 486 | throw std::runtime_error("throw on request"); 487 | } 488 | } 489 | State* state = nullptr; 490 | }; 491 | static_assert(!detail::can_use_inplace_buffer); 492 | 493 | State srcState; 494 | State dstState; 495 | 496 | function srcFn(Callable{ srcState }); 497 | function dstFn(Callable{ dstState }); 498 | 499 | srcFn(); 500 | dstFn(); 501 | 502 | REQUIRE(srcState.callCount == 1); 503 | REQUIRE(dstState.callCount == 1); 504 | 505 | srcState.throwOnCopy = true; 506 | 507 | REQUIRE_THROWS_AS(dstFn = srcFn, std::runtime_error); 508 | 509 | // srcFn and dstFn must not be emptied even if assignment throws 510 | REQUIRE_NOTHROW(srcFn()); 511 | REQUIRE_NOTHROW(dstFn()); 512 | 513 | // srcFn and dstFn must keep their state 514 | REQUIRE(srcState.callCount == 2); 515 | REQUIRE(dstState.callCount == 2); 516 | 517 | // The next copy will succeed 518 | srcState.throwOnCopy = false; 519 | REQUIRE_NOTHROW(dstFn = srcFn); 520 | 521 | // Both functions are usable 522 | REQUIRE_NOTHROW(srcFn()); 523 | REQUIRE_NOTHROW(dstFn()); 524 | 525 | // After assignment, dstFn and srcFn refer the same state - srcState 526 | REQUIRE(srcState.callCount == 4); 527 | REQUIRE(dstState.callCount == 2); 528 | } 529 | 530 | TEST_CASE("assignment of variously allocated functions", "[function]") 531 | { 532 | int heapCalls = 0; 533 | auto onHeap = [&heapCalls, largeVar = std::array()]() mutable { 534 | std::fill(largeVar.begin(), largeVar.end(), "large string to be allocated on heap instead of stack"); 535 | ++heapCalls; 536 | }; 537 | int stackCalls = 0; 538 | auto onStack = [&stackCalls] { 539 | ++stackCalls; 540 | }; 541 | 542 | static_assert(detail::can_use_inplace_buffer>); 543 | static_assert(!detail::can_use_inplace_buffer>); 544 | 545 | using Fn = function; 546 | { 547 | Fn heap(onHeap); 548 | Fn stack(onStack); 549 | heap = stack; 550 | heap(); 551 | REQUIRE(stackCalls == 1); 552 | REQUIRE(heapCalls == 0); 553 | } 554 | { 555 | Fn heap(onHeap); 556 | Fn stack(onStack); 557 | stack = heap; 558 | stack(); 559 | REQUIRE(stackCalls == 1); 560 | REQUIRE(heapCalls == 1); 561 | } 562 | { 563 | Fn heap(onHeap); 564 | Fn heap1(onHeap); 565 | heap = heap1; 566 | heap(); 567 | REQUIRE(stackCalls == 1); 568 | REQUIRE(heapCalls == 2); 569 | } 570 | { 571 | Fn stack(onStack); 572 | Fn stack1(onStack); 573 | stack = stack1; 574 | stack(); 575 | REQUIRE(stackCalls == 2); 576 | REQUIRE(heapCalls == 2); 577 | } 578 | { 579 | Fn heap(onHeap); 580 | Fn empty; 581 | heap = empty; 582 | REQUIRE_THROWS(heap()); 583 | REQUIRE(stackCalls == 2); 584 | REQUIRE(heapCalls == 2); 585 | } 586 | { 587 | Fn stack(onStack); 588 | Fn empty; 589 | stack = empty; 590 | REQUIRE_THROWS(stack()); 591 | REQUIRE(stackCalls == 2); 592 | REQUIRE(heapCalls == 2); 593 | } 594 | { 595 | Fn empty; 596 | Fn heap(onHeap); 597 | empty = heap; 598 | empty(); 599 | REQUIRE(stackCalls == 2); 600 | REQUIRE(heapCalls == 3); 601 | } 602 | { 603 | Fn empty; 604 | Fn stack(onStack); 605 | empty = stack; 606 | empty(); 607 | REQUIRE(stackCalls == 3); 608 | REQUIRE(heapCalls == 3); 609 | } 610 | { 611 | Fn empty; 612 | Fn empty1; 613 | empty = empty1; 614 | REQUIRE_THROWS(empty()); 615 | REQUIRE(stackCalls == 3); 616 | REQUIRE(heapCalls == 3); 617 | } 618 | } 619 | -------------------------------------------------------------------------------- /tests/libfastsignals_unit_tests/signal_tests.cpp: -------------------------------------------------------------------------------- 1 | #include "catch2/catch.hpp" 2 | #include "libfastsignals/include/signal.h" 3 | #include 4 | 5 | using namespace is::signals; 6 | using namespace std::literals; 7 | 8 | namespace 9 | { 10 | template 11 | class any_of_combiner 12 | { 13 | public: 14 | static_assert(std::is_same_v); 15 | 16 | using result_type = bool; 17 | 18 | template 19 | void operator()(TRef&& value) 20 | { 21 | m_result = m_result || bool(value); 22 | } 23 | 24 | result_type get_value() const 25 | { 26 | return m_result; 27 | } 28 | 29 | private: 30 | result_type m_result = {}; 31 | }; 32 | } // namespace 33 | 34 | TEST_CASE("Can connect a few slots and emit", "[signal]") 35 | { 36 | signal valueChanged; 37 | 38 | int value1 = 0; 39 | int value2 = 0; 40 | valueChanged.connect([&value1](int value) { 41 | value1 = value; 42 | }); 43 | valueChanged.connect([&value2](int value) { 44 | value2 = value; 45 | }); 46 | REQUIRE(value1 == 0); 47 | REQUIRE(value2 == 0); 48 | 49 | valueChanged(10); 50 | REQUIRE(value1 == 10); 51 | REQUIRE(value2 == 10); 52 | } 53 | 54 | TEST_CASE("Can safely pass rvalues", "[signal]") 55 | { 56 | const std::string expected = "If the type T is a reference type, provides the member typedef type which is the type referred to by T. Otherwise type is T."; 57 | std::string passedValue = expected; 58 | signal valueChanged; 59 | 60 | std::string value1; 61 | std::string value2; 62 | valueChanged.connect([&value1](std::string value) { 63 | value1 = value; 64 | }); 65 | valueChanged.connect([&value2](std::string value) { 66 | value2 = value; 67 | }); 68 | 69 | valueChanged(std::move(passedValue)); 70 | REQUIRE(value1 == expected); 71 | REQUIRE(value2 == expected); 72 | } 73 | 74 | TEST_CASE("Can pass mutable ref", "[signal]") 75 | { 76 | const std::string expected = "If the type T is a reference type, provides the member typedef type which is the type referred to by T. Otherwise type is T."; 77 | signal valueChanged; 78 | 79 | std::string passedValue; 80 | valueChanged.connect([expected](std::string& value) { 81 | value = expected; 82 | }); 83 | valueChanged(passedValue); 84 | 85 | REQUIRE(passedValue == expected); 86 | } 87 | 88 | TEST_CASE("Can disconnect slot with explicit call", "[signal]") 89 | { 90 | signal valueChanged; 91 | 92 | int value1 = 0; 93 | int value2 = 0; 94 | int value3 = 0; 95 | auto conn1 = valueChanged.connect([&value1](int value) { 96 | value1 = value; 97 | }); 98 | auto conn2 = valueChanged.connect([&value2](int value) { 99 | value2 = value; 100 | }); 101 | valueChanged.connect([&value3](int value) { 102 | value3 = value; 103 | }); 104 | REQUIRE(value1 == 0); 105 | REQUIRE(value2 == 0); 106 | REQUIRE(value3 == 0); 107 | 108 | valueChanged(10); 109 | REQUIRE(value1 == 10); 110 | REQUIRE(value2 == 10); 111 | REQUIRE(value3 == 10); 112 | 113 | conn2.disconnect(); 114 | valueChanged(-99); 115 | REQUIRE(value1 == -99); 116 | REQUIRE(value2 == 10); 117 | REQUIRE(value3 == -99); 118 | 119 | conn1.disconnect(); 120 | valueChanged(17); 121 | REQUIRE(value1 == -99); 122 | REQUIRE(value2 == 10); 123 | REQUIRE(value3 == 17); 124 | } 125 | 126 | TEST_CASE("Can disconnect slot with scoped_connection", "[signal]") 127 | { 128 | signal valueChanged; 129 | 130 | int value1 = 0; 131 | int value2 = 0; 132 | int value3 = 0; 133 | { 134 | scoped_connection conn1 = valueChanged.connect([&value1](int value) { 135 | value1 = value; 136 | }); 137 | { 138 | scoped_connection conn2 = valueChanged.connect([&value2](int value) { 139 | value2 = value; 140 | }); 141 | valueChanged.connect([&value3](int value) { 142 | value3 = value; 143 | }); 144 | REQUIRE(value1 == 0); 145 | REQUIRE(value2 == 0); 146 | REQUIRE(value3 == 0); 147 | 148 | valueChanged(10); 149 | REQUIRE(value1 == 10); 150 | REQUIRE(value2 == 10); 151 | REQUIRE(value3 == 10); 152 | } 153 | 154 | // conn2 disconnected. 155 | valueChanged(-99); 156 | REQUIRE(value1 == -99); 157 | REQUIRE(value2 == 10); 158 | REQUIRE(value3 == -99); 159 | } 160 | 161 | // conn1 disconnected. 162 | valueChanged(17); 163 | REQUIRE(value1 == -99); 164 | REQUIRE(value2 == 10); 165 | REQUIRE(value3 == 17); 166 | } 167 | 168 | TEST_CASE("Can disconnect all", "[signal]") 169 | { 170 | signal valueChanged; 171 | 172 | int value1 = 0; 173 | int value2 = 0; 174 | int value3 = 0; 175 | valueChanged.connect([&value1](int value) { 176 | value1 = value; 177 | }); 178 | valueChanged.connect([&value2](int value) { 179 | value2 = value; 180 | }); 181 | valueChanged.connect([&value3](int value) { 182 | value3 = value; 183 | }); 184 | REQUIRE(value1 == 0); 185 | REQUIRE(value2 == 0); 186 | REQUIRE(value3 == 0); 187 | 188 | valueChanged(63); 189 | REQUIRE(value1 == 63); 190 | REQUIRE(value2 == 63); 191 | REQUIRE(value3 == 63); 192 | 193 | valueChanged.disconnect_all_slots(); 194 | valueChanged(101); 195 | REQUIRE(value1 == 63); 196 | REQUIRE(value2 == 63); 197 | REQUIRE(value3 == 63); 198 | } 199 | 200 | TEST_CASE("Can disconnect inside slot", "[signal]") 201 | { 202 | signal valueChanged; 203 | 204 | int value1 = 0; 205 | int value2 = 0; 206 | int value3 = 0; 207 | connection conn2; 208 | valueChanged.connect([&value1](int value) { 209 | value1 = value; 210 | }); 211 | conn2 = valueChanged.connect([&](int value) { 212 | value2 = value; 213 | conn2.disconnect(); 214 | }); 215 | valueChanged.connect([&value3](int value) { 216 | value3 = value; 217 | }); 218 | REQUIRE(value1 == 0); 219 | REQUIRE(value2 == 0); 220 | REQUIRE(value3 == 0); 221 | 222 | valueChanged(63); 223 | REQUIRE(value1 == 63); 224 | REQUIRE(value2 == 63); 225 | REQUIRE(value3 == 63); 226 | 227 | valueChanged(101); 228 | REQUIRE(value1 == 101); 229 | REQUIRE(value2 == 63); // disconnected in slot. 230 | REQUIRE(value3 == 101); 231 | } 232 | 233 | TEST_CASE("Disconnects OK if signal dead first", "[signal]") 234 | { 235 | connection conn2; 236 | { 237 | scoped_connection conn1; 238 | { 239 | signal valueChanged; 240 | conn2 = valueChanged.connect([](int) { 241 | }); 242 | // Just unused. 243 | valueChanged.connect([](int) { 244 | }); 245 | conn1 = valueChanged.connect([](int) { 246 | }); 247 | } 248 | REQUIRE(conn2.connected()); 249 | REQUIRE(conn1.connected()); 250 | conn2.disconnect(); 251 | REQUIRE(!conn2.connected()); 252 | REQUIRE(conn1.connected()); 253 | } 254 | conn2.disconnect(); 255 | } 256 | 257 | TEST_CASE("Returns last called slot result with default combiner", "[signal]") 258 | { 259 | connection conn2; 260 | { 261 | scoped_connection conn1; 262 | { 263 | signal absSignal; 264 | conn2 = absSignal.connect([](int value) { 265 | return value * value; 266 | }); 267 | conn1 = absSignal.connect([](int value) { 268 | return abs(value); 269 | }); 270 | absSignal(-1); 271 | 272 | REQUIRE(absSignal(45) == 45); 273 | REQUIRE(absSignal(-1) == 1); 274 | REQUIRE(absSignal(-177) == 177); 275 | REQUIRE(absSignal(0) == 0); 276 | } 277 | REQUIRE(conn2.connected()); 278 | conn2.disconnect(); 279 | REQUIRE(!conn2.connected()); 280 | } 281 | conn2.disconnect(); 282 | REQUIRE(!conn2.connected()); 283 | } 284 | 285 | TEST_CASE("Works with custom any_of combiner", "[signal]") 286 | { 287 | using cancellable_signal = signal; 288 | cancellable_signal startRequested; 289 | auto conn1 = startRequested.connect([](std::string op) { 290 | return op == "1"; 291 | }); 292 | auto conn2 = startRequested.connect([](std::string op) { 293 | return op == "1" || op == "2"; 294 | }); 295 | REQUIRE(startRequested("0") == false); 296 | REQUIRE(startRequested("1") == true); 297 | REQUIRE(startRequested("2") == true); 298 | REQUIRE(startRequested("3") == false); 299 | conn1.disconnect(); 300 | conn2.disconnect(); 301 | REQUIRE(startRequested("0") == false); 302 | REQUIRE(startRequested("1") == false); 303 | REQUIRE(startRequested("2") == false); 304 | REQUIRE(startRequested("3") == false); 305 | } 306 | 307 | TEST_CASE("Can release scoped connection", "[signal]") 308 | { 309 | int value2 = 0; 310 | int value3 = 0; 311 | signal valueChanged; 312 | connection conn1; 313 | { 314 | scoped_connection conn2; 315 | scoped_connection conn3; 316 | conn2 = valueChanged.connect([&value2](int x) { 317 | value2 = x; 318 | }); 319 | conn3 = valueChanged.connect([&value3](int x) { 320 | value3 = x; 321 | }); 322 | 323 | valueChanged(42); 324 | REQUIRE(value2 == 42); 325 | REQUIRE(value3 == 42); 326 | REQUIRE(conn2.connected()); 327 | REQUIRE(conn3.connected()); 328 | REQUIRE(!conn1.connected()); 329 | 330 | conn1 = conn3.release(); 331 | REQUIRE(conn2.connected()); 332 | REQUIRE(!conn3.connected()); 333 | REQUIRE(conn1.connected()); 334 | valueChanged(144); 335 | REQUIRE(value2 == 144); 336 | REQUIRE(value3 == 144); 337 | } 338 | 339 | // conn2 disconnected, conn1 connected. 340 | valueChanged(17); 341 | REQUIRE(value2 == 144); 342 | REQUIRE(value3 == 17); 343 | REQUIRE(conn1.connected()); 344 | 345 | conn1.disconnect(); 346 | valueChanged(90); 347 | REQUIRE(value2 == 144); 348 | REQUIRE(value3 == 17); 349 | } 350 | 351 | TEST_CASE("Can use signal with more than one argument", "[signal]") 352 | { 353 | signal)> event; 354 | 355 | int value1 = 0; 356 | std::string value2; 357 | std::vector value3; 358 | event.connect([&](int v1, const std::string& v2, const std::vector& v3) { 359 | value1 = v1; 360 | value2 = v2; 361 | value3 = v3; 362 | }); 363 | 364 | event(9815, "using namespace std::literals!"s, std::vector{ "std::vector"s, "using namespace std::literals"s }); 365 | REQUIRE(value1 == 9815); 366 | REQUIRE(value2 == "using namespace std::literals!"s); 367 | REQUIRE(value3 == std::vector{ "std::vector"s, "using namespace std::literals"s }); 368 | } 369 | 370 | TEST_CASE("Can blocks slots using shared_connection_block", "[signal]") 371 | { 372 | bool callbackShouldBeCalled = true; 373 | bool callbackCalled = false; 374 | const int value = 123; 375 | signal event; 376 | auto conn = event.connect([&](int gotValue) { 377 | CHECK(gotValue == value); 378 | callbackCalled = true; 379 | if (!callbackShouldBeCalled) 380 | { 381 | FAIL("callback is blocked and should not be called"); 382 | } 383 | }, 384 | advanced_tag{}); 385 | event(value); 386 | REQUIRE(callbackCalled); 387 | shared_connection_block block(conn); 388 | callbackShouldBeCalled = false; 389 | callbackCalled = false; 390 | event(value); 391 | REQUIRE(!callbackCalled); 392 | block.unblock(); 393 | callbackShouldBeCalled = true; 394 | event(value); 395 | REQUIRE(callbackCalled); 396 | } 397 | 398 | TEST_CASE("Other slots are unaffected by the block", "[signal]") 399 | { 400 | bool callback1Called = false; 401 | bool callback2Called = false; 402 | const int value = 123; 403 | signal event; 404 | auto conn1 = event.connect([&](int gotValue) { 405 | CHECK(gotValue == value); 406 | callback1Called = true; 407 | }, 408 | advanced_tag{}); 409 | auto conn2 = event.connect([&](int) { 410 | callback2Called = true; 411 | FAIL("callback is blocked and should not be called"); 412 | }, 413 | advanced_tag{}); 414 | shared_connection_block block(conn2); 415 | event(value); 416 | REQUIRE(callback1Called); 417 | REQUIRE(!callback2Called); 418 | } 419 | 420 | TEST_CASE("Multiple blocks block until last one is unblocked", "[signal]") 421 | { 422 | bool callbackShouldBeCalled = false; 423 | bool callbackCalled = false; 424 | const int value = 123; 425 | signal event; 426 | auto conn = event.connect([&](int gotValue) { 427 | CHECK(gotValue == value); 428 | callbackCalled = true; 429 | if (!callbackShouldBeCalled) 430 | { 431 | FAIL("callback is blocked and should not be called"); 432 | } 433 | }, 434 | advanced_tag{}); 435 | shared_connection_block block1(conn); 436 | shared_connection_block block2(conn); 437 | event(value); 438 | REQUIRE(!callbackCalled); 439 | block1.unblock(); 440 | event(value); 441 | REQUIRE(!callbackCalled); 442 | block1.block(); 443 | block2.unblock(); 444 | event(value); 445 | REQUIRE(!callbackCalled); 446 | block1.unblock(); 447 | callbackShouldBeCalled = true; 448 | event(value); 449 | REQUIRE(callbackCalled); 450 | } 451 | 452 | TEST_CASE("Can copy and move shared_connection_block objects", "[signal]") 453 | { 454 | bool callbackShouldBeCalled = false; 455 | bool callbackCalled = false; 456 | const int value = 123; 457 | signal event; 458 | auto conn = event.connect([&](int gotValue) { 459 | CHECK(gotValue == value); 460 | callbackCalled = true; 461 | if (!callbackShouldBeCalled) 462 | { 463 | FAIL("callback is blocked and should not be called"); 464 | } 465 | }, 466 | advanced_tag{}); 467 | shared_connection_block block1(conn); 468 | 469 | shared_connection_block block2(block1); 470 | event(value); 471 | REQUIRE(block1.blocking()); 472 | REQUIRE(block2.blocking()); 473 | REQUIRE(!callbackCalled); 474 | 475 | shared_connection_block block3(std::move(block2)); 476 | event(value); 477 | REQUIRE(block1.blocking()); 478 | REQUIRE(!block2.blocking()); 479 | REQUIRE(block3.blocking()); 480 | REQUIRE(!callbackCalled); 481 | 482 | block2 = block3; 483 | event(value); 484 | REQUIRE(block1.blocking()); 485 | REQUIRE(block2.blocking()); 486 | REQUIRE(block3.blocking()); 487 | REQUIRE(!callbackCalled); 488 | 489 | block3 = std::move(block2); 490 | event(value); 491 | REQUIRE(block1.blocking()); 492 | REQUIRE(!block2.blocking()); 493 | REQUIRE(block3.blocking()); 494 | REQUIRE(!callbackCalled); 495 | 496 | block3 = shared_connection_block(conn, false); 497 | event(value); 498 | REQUIRE(block1.blocking()); 499 | REQUIRE(!block2.blocking()); 500 | REQUIRE(!block3.blocking()); 501 | REQUIRE(!callbackCalled); 502 | 503 | block1.unblock(); 504 | callbackShouldBeCalled = true; 505 | event(value); 506 | REQUIRE(!block1.blocking()); 507 | REQUIRE(!block2.blocking()); 508 | REQUIRE(!block3.blocking()); 509 | REQUIRE(callbackCalled); 510 | } 511 | 512 | TEST_CASE("Unblocks when shared_connection_block goes out of scope") 513 | { 514 | bool callbackCalled = false; 515 | const int value = 123; 516 | signal event; 517 | auto conn = event.connect([&](int gotValue) { 518 | CHECK(gotValue == value); 519 | callbackCalled = true; 520 | }, 521 | advanced_tag{}); 522 | 523 | callbackCalled = false; 524 | event(value); 525 | CHECK(callbackCalled); 526 | 527 | { 528 | callbackCalled = false; 529 | shared_connection_block block(conn); 530 | event(value); 531 | CHECK(!callbackCalled); 532 | 533 | { 534 | callbackCalled = false; 535 | shared_connection_block block2(conn); 536 | event(value); 537 | CHECK(!callbackCalled); 538 | } 539 | } 540 | 541 | callbackCalled = false; 542 | event(value); 543 | CHECK(callbackCalled); 544 | } 545 | 546 | TEST_CASE("Can disconnect advanced slot using advanced_scoped_connection", "[signal]") 547 | { 548 | signal valueChanged; 549 | 550 | int value1 = 0; 551 | int value2 = 0; 552 | int value3 = 0; 553 | { 554 | advanced_scoped_connection conn1 = valueChanged.connect([&value1](int value) { 555 | value1 = value; 556 | }, 557 | advanced_tag{}); 558 | { 559 | advanced_scoped_connection conn2 = valueChanged.connect([&value2](int value) { 560 | value2 = value; 561 | }, 562 | advanced_tag{}); 563 | valueChanged.connect([&value3](int value) { 564 | value3 = value; 565 | }); 566 | REQUIRE(value1 == 0); 567 | REQUIRE(value2 == 0); 568 | REQUIRE(value3 == 0); 569 | 570 | valueChanged(10); 571 | REQUIRE(value1 == 10); 572 | REQUIRE(value2 == 10); 573 | REQUIRE(value3 == 10); 574 | } 575 | 576 | // conn2 disconnected. 577 | valueChanged(-99); 578 | REQUIRE(value1 == -99); 579 | REQUIRE(value2 == 10); 580 | REQUIRE(value3 == -99); 581 | } 582 | 583 | // conn1 disconnected. 584 | valueChanged(17); 585 | REQUIRE(value1 == -99); 586 | REQUIRE(value2 == 10); 587 | REQUIRE(value3 == 17); 588 | } 589 | 590 | TEST_CASE("Can move signal", "[signal]") 591 | { 592 | signal src; 593 | 594 | int srcFireCount = 0; 595 | auto srcConn = src.connect([&srcFireCount] { 596 | ++srcFireCount; 597 | }); 598 | 599 | src(); 600 | REQUIRE(srcFireCount == 1); 601 | 602 | auto dst = std::move(src); 603 | 604 | int dstFireCount = 0; 605 | auto dstConn = dst.connect([&dstFireCount] { 606 | ++dstFireCount; 607 | }); 608 | 609 | dst(); 610 | REQUIRE(srcFireCount == 2); 611 | REQUIRE(dstFireCount == 1); 612 | 613 | srcConn.disconnect(); 614 | dstConn.disconnect(); 615 | dst(); 616 | REQUIRE(srcFireCount == 2); 617 | REQUIRE(dstFireCount == 1); 618 | } 619 | 620 | TEST_CASE("Can swap signals", "[signal]") 621 | { 622 | signal s1; 623 | signal s2; 624 | 625 | int s1FireCount = 0; 626 | int s2FireCount = 0; 627 | 628 | s1.connect([&s1FireCount] { 629 | ++s1FireCount; 630 | }); 631 | 632 | s2.connect([&s2FireCount] { 633 | ++s2FireCount; 634 | }); 635 | 636 | std::swap(s1, s2); 637 | 638 | s1(); 639 | REQUIRE(s1FireCount == 0); 640 | REQUIRE(s2FireCount == 1); 641 | 642 | s2(); 643 | REQUIRE(s1FireCount == 1); 644 | REQUIRE(s2FireCount == 1); 645 | } 646 | 647 | TEST_CASE("Signal can be destroyed inside its slot and will call the rest of its slots", "[signal]") 648 | { 649 | std::optional> s; 650 | s.emplace(); 651 | s->connect([&] { 652 | s.reset(); 653 | }); 654 | bool called = false; 655 | s->connect([&] { 656 | called = true; 657 | }); 658 | (*s)(); 659 | CHECK(called); 660 | } 661 | 662 | TEST_CASE("Signal can be used as a slot for another signal", "[signal]") 663 | { 664 | signal s1; 665 | bool called = false; 666 | s1.connect([&] { 667 | called = true; 668 | }); 669 | 670 | signal s2; 671 | s2.connect(s1); 672 | 673 | s2(); 674 | 675 | CHECK(called); 676 | } 677 | 678 | // memory leak fix 679 | TEST_CASE("Releases lambda and its captured const data", "[signal]") 680 | { 681 | struct Captured 682 | { 683 | Captured(bool& released) 684 | : m_released(released) 685 | { 686 | } 687 | 688 | ~Captured() 689 | { 690 | m_released = true; 691 | } 692 | 693 | private: 694 | bool& m_released; 695 | }; 696 | 697 | bool released = false; 698 | 699 | { 700 | const auto captured = std::make_shared(released); 701 | 702 | signal changeSignal; 703 | changeSignal.connect([captured]{}); 704 | } 705 | 706 | CHECK(released); 707 | } --------------------------------------------------------------------------------