├── .github └── workflows │ └── unit-tests.yml ├── .gitignore ├── CMakeLists.txt ├── CMakePresets.json ├── LICENSE ├── cmake ├── PalSigslotConfig.cmake.in └── SigslotUtils.cmake ├── example ├── CMakeLists.txt ├── arguments.cpp ├── basic.cpp ├── connection.cpp ├── default-arguments.cpp ├── disconnection.cpp ├── lambdas.cpp ├── overloads.cpp └── slot-group.cpp ├── include └── sigslot │ ├── adapter │ ├── boost.hpp │ └── qt.hpp │ └── signal.hpp ├── readme.md └── test ├── CMakeLists.txt ├── bench-slots.cpp ├── boost-tracking.cpp ├── function-traits.cpp ├── observer.cpp ├── pmf-disconnection.cpp ├── qt-tracking.cpp ├── recursive.cpp ├── signal-extended.cpp ├── signal-performance.cpp ├── signal-pmf.cpp ├── signal-threaded.cpp ├── signal-tracking.cpp ├── signal.cpp ├── slots-groups.cpp └── test-common.h /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: Sigslot Unit Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | - ci 9 | 10 | pull_request: 11 | branches: 12 | - master 13 | - main 14 | 15 | jobs: 16 | Test: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | build_type: ['Debug', 'Release'] 22 | os: [ubuntu-latest, macos-latest, windows-latest] 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Configure sigslot 26 | run: | 27 | cmake -E make_directory build 28 | cmake \ 29 | -DCMAKE_BUILD_TYPE="${{ matrix.build_type }}" \ 30 | -DSIGSLOT_ENABLE_MANY_WARNINGS=ON \ 31 | -DSIGSLOT_COMPILE_TESTS=ON \ 32 | -DSIGSLOT_COMPILE_EXAMPLES=ON \ 33 | -B build -S . 34 | shell: bash 35 | - name: Build Sigslot Examples 36 | run: cmake --build build --target sigslot-examples --config "${{ matrix.build_type }}" 37 | - name: Build & Run Sigslot Tests 38 | run: cmake --build build --target sigslot-tests --config "${{ matrix.build_type }}" 39 | Sanitizer: 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | os: [ubuntu-latest, macos-latest, windows-latest] 45 | sanitizer: ['SANITIZE_ADDRESS', 'SANITIZE_THREADS', 'SANITIZE_UNDEFINED'] 46 | steps: 47 | - uses: actions/checkout@v4 48 | - name: Configure sigslot 49 | run: | 50 | cmake -E make_directory build 51 | cmake \ 52 | -DCMAKE_BUILD_TYPE=RelWithDebInfo \ 53 | -DSIGSLOT_ENABLE_MANY_WARNINGS=ON \ 54 | -DSIGSLOT_ENABLE_PROFILING=ON \ 55 | -DSIGSLOT_${{ matrix.sanitizer }}=ON \ 56 | -DSIGSLOT_COMPILE_TESTS=ON \ 57 | -DSIGSLOT_COMPILE_EXAMPLES=ON \ 58 | -B build -S . 59 | shell: bash 60 | - name: Build Sigslot Examples 61 | run: cmake --build build --target sigslot-examples --config RelWithDebInfo 62 | - name: Build & Run Sigslot Tests 63 | run: cmake --build build --target sigslot-tests --config RelWithDebInfo 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Allow in source build 2 | [Bb]uild*/ 3 | # Ignore qt creator generated CMakeLists.txt.users 4 | *.users 5 | # Ignore CLion project files 6 | *.idea 7 | # Ignore CLion default generated build folder 8 | cmake-build*/ 9 | # For macOs users 10 | .DS_Store 11 | # Ignore sublime merge file that can be created on merge fail 12 | Sublimerge* 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(pal_sigslot VERSION 1.2.2 LANGUAGES CXX) 4 | 5 | ### main project 6 | set(SIGSLOT_MAIN_PROJECT OFF) 7 | if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 8 | set(SIGSLOT_MAIN_PROJECT ON) 9 | endif() 10 | 11 | include(cmake/SigslotUtils.cmake) 12 | 13 | ### options 14 | option(SIGSLOT_COMPILE_EXAMPLES "Compile optional examples" ${SIGSLOT_MAIN_PROJECT}) 15 | option(SIGSLOT_COMPILE_TESTS "Compile tests" ${SIGSLOT_MAIN_PROJECT}) 16 | option(SIGSLOT_REDUCE_COMPILE_TIME "Attempt at reducing code size and compilation time" OFF) 17 | option(SIGSLOT_ENABLE_INSTALL "Create install target" ${SIGSLOT_MAIN_PROJECT}) 18 | 19 | find_package(Threads REQUIRED) 20 | 21 | ### sigslot library 22 | add_library(sigslot INTERFACE) 23 | add_library(Pal::Sigslot ALIAS sigslot) 24 | target_include_directories(sigslot INTERFACE 25 | $ 26 | $ 27 | ) 28 | target_compile_definitions(sigslot INTERFACE 29 | $<$:SIGSLOT_REDUCE_COMPILE_TIME> 30 | ) 31 | target_link_options(sigslot INTERFACE 32 | # We deactivate ICF on windows compilers because it interferes with signal 33 | # disconnection by making identical functions not unique. 34 | $<$:/OPT:NOICF> 35 | $<$:/OPT:NOICF> 36 | ) 37 | target_link_libraries(sigslot INTERFACE Threads::Threads) 38 | set_target_properties(sigslot PROPERTIES EXPORT_NAME Sigslot) 39 | 40 | if(SIGSLOT_ENABLE_INSTALL) 41 | #installation 42 | include(GNUInstallDirs) 43 | include(CMakePackageConfigHelpers) 44 | 45 | configure_package_config_file( 46 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake/PalSigslotConfig.cmake.in 47 | ${CMAKE_CURRENT_BINARY_DIR}/PalSigslotConfig.cmake 48 | INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/PalSigslot" 49 | ) 50 | 51 | write_basic_package_version_file( 52 | ${CMAKE_CURRENT_BINARY_DIR}/PalSigslotConfigVersion.cmake 53 | VERSION ${PROJECT_VERSION} 54 | COMPATIBILITY SameMajorVersion 55 | ) 56 | 57 | install( 58 | TARGETS sigslot 59 | EXPORT PalSigslotTargets 60 | DESTINATION ${CMAKE_INSTALL_LIBDIR} 61 | ) 62 | install( 63 | DIRECTORY include/ 64 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 65 | ) 66 | install( 67 | EXPORT PalSigslotTargets 68 | FILE PalSigslotTargets.cmake 69 | NAMESPACE Pal:: 70 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/PalSigslot" 71 | ) 72 | install( 73 | FILES ${CMAKE_CURRENT_BINARY_DIR}/PalSigslotConfig.cmake 74 | ${CMAKE_CURRENT_BINARY_DIR}/PalSigslotConfigVersion.cmake 75 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/PalSigslot" 76 | ) 77 | export(TARGETS sigslot 78 | NAMESPACE Pal:: 79 | FILE ${CMAKE_CURRENT_BINARY_DIR}/PalSigslotTargets.cmake 80 | ) 81 | 82 | # Install the Config and ConfigVersion files 83 | install( 84 | FILES ${version_file} 85 | ${config_file} 86 | DESTINATION ${cmake_dir} 87 | COMPONENT ${projectname_target}Export 88 | ) 89 | endif() 90 | 91 | # dependencies for examples and tests 92 | if(SIGSLOT_COMPILE_TESTS OR SIGSLOT_COMPILE_EXAMPLES) 93 | find_package(Boost COMPONENTS system QUIET) # test of boost bridge with smart pointers 94 | find_package(Qt5 COMPONENTS Core Widgets Gui QUIET) # optional test of Qt bridge 95 | endif() 96 | 97 | if(SIGSLOT_COMPILE_EXAMPLES) 98 | add_subdirectory(example) 99 | endif() 100 | 101 | if(SIGSLOT_COMPILE_TESTS) 102 | enable_testing() 103 | add_subdirectory(test) 104 | endif() 105 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 6, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 25, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "base", 11 | "description": "hidden base preset containing common setup", 12 | "hidden": true, 13 | "generator": "Ninja Multi-Config", 14 | "binaryDir": "${sourceDir}/build/${presetName}", 15 | "cacheVariables": { 16 | "CMAKE_CONFIGURATION_TYPES": "Debug;Release;RelWithDebInfo", 17 | "SIGSLOT_ENABLE_MANY_WARNINGS": true, 18 | "SIGSLOT_COMPILE_TESTS": true, 19 | "SIGSLOT_COMPILE_EXAMPLES": true 20 | } 21 | }, 22 | { 23 | "name": "gcc", 24 | "hidden": true, 25 | "cacheVariables": { 26 | "CMAKE_C_COMPILER": "gcc", 27 | "CMAKE_CXX_COMPILER": "g++" 28 | } 29 | }, 30 | { 31 | "name": "clang", 32 | "hidden": true, 33 | "cacheVariables": { 34 | "CMAKE_C_COMPILER": "clang", 35 | "CMAKE_CXX_COMPILER": "clang++" 36 | } 37 | }, 38 | { 39 | "name": "windows", 40 | "displayName": "Windows x64 MinGW", 41 | "description": "Windows build MinGW with the first toochain in PATH", 42 | "inherits": "base", 43 | "condition": { 44 | "type": "equals", 45 | "lhs": "${hostSystemName}", 46 | "rhs": "Windows" 47 | } 48 | }, 49 | { 50 | "name": "linux", 51 | "displayName": "Linux", 52 | "description": "Linux build with the first toochain in PATH", 53 | "inherits": "base", 54 | "condition": { 55 | "type": "equals", 56 | "lhs": "${hostSystemName}", 57 | "rhs": "Linux" 58 | } 59 | }, 60 | { 61 | "name": "windows-gcc", 62 | "displayName": "Windows x64 MinGW GCC", 63 | "inherits": ["gcc", "windows"] 64 | }, 65 | { 66 | "name": "windows-clang", 67 | "displayName": "Windows x64 MinGW Clang", 68 | "inherits": ["clang", "windows"] 69 | }, 70 | { 71 | "name": "linux-gcc", 72 | "displayName": "Linux x64 GCC", 73 | "inherits": ["gcc", "linux"] 74 | }, 75 | { 76 | "name": "linux-clang", 77 | "displayName": "Linux x64 Clang", 78 | "inherits": ["clang", "linux"] 79 | } 80 | ], 81 | "buildPresets": [ 82 | { 83 | "name": "windows", 84 | "hidden": true, 85 | "condition": { 86 | "type": "equals", 87 | "lhs": "${hostSystemName}", 88 | "rhs": "Windows" 89 | }, 90 | "targets": [ 91 | "sigslot-examples", 92 | "sigslot-tests" 93 | ] 94 | }, 95 | { 96 | "name": "linux", 97 | "hidden": true, 98 | "condition": { 99 | "type": "equals", 100 | "lhs": "${hostSystemName}", 101 | "rhs": "Linux" 102 | }, 103 | "targets": [ 104 | "sigslot-examples", 105 | "sigslot-tests" 106 | ] 107 | }, 108 | { 109 | "name": "windows-gcc-rel", 110 | "displayName": "Windows x64 MinGW GCC Release", 111 | "inherits": "windows", 112 | "configurePreset": "windows-gcc", 113 | "configuration": "Release" 114 | }, 115 | { 116 | "name": "windows-gcc-prof", 117 | "displayName": "Windows x64 MinGW GCC RelWithDebInfo", 118 | "inherits": "windows", 119 | "configurePreset": "windows-gcc", 120 | "configuration": "RelWithDebInfo" 121 | }, 122 | { 123 | "name": "windows-gcc-deb", 124 | "displayName": "Windows x64 MinGW GCC Debug", 125 | "inherits": "windows", 126 | "configurePreset": "windows-gcc", 127 | "configuration": "Debug" 128 | }, 129 | { 130 | "name": "windows-clang-rel", 131 | "displayName": "Windows x64 MinGW Clang Release", 132 | "inherits": "windows", 133 | "configurePreset": "windows-clang", 134 | "configuration": "Release" 135 | }, 136 | { 137 | "name": "windows-clang-prof", 138 | "displayName": "Windows x64 MinGW Clang RelWithDebInfo", 139 | "inherits": "windows", 140 | "configurePreset": "windows-clang", 141 | "configuration": "RelWithDebInfo" 142 | }, 143 | { 144 | "name": "windows-clang-deb", 145 | "displayName": "Windows x64 MinGW Clang Debug", 146 | "inherits": "windows", 147 | "configurePreset": "windows-clang", 148 | "configuration": "Debug" 149 | }, 150 | { 151 | "name": "linux-gcc-rel", 152 | "displayName": "Linux x64 GCC Release", 153 | "inherits": "linux", 154 | "configurePreset": "linux-gcc", 155 | "configuration": "Release" 156 | }, 157 | { 158 | "name": "linux-gcc-prof", 159 | "displayName": "Linux x64 GCC RelWithDebInfo", 160 | "inherits": "linux", 161 | "configurePreset": "linux-gcc", 162 | "configuration": "RelWithDebInfo" 163 | }, 164 | { 165 | "name": "linux-gcc-deb", 166 | "displayName": "Linux x64 GCC Debug", 167 | "inherits": "linux", 168 | "configurePreset": "linux-gcc", 169 | "configuration": "Debug" 170 | }, 171 | { 172 | "name": "linux-clang-rel", 173 | "displayName": "Linux x64 Clang Release", 174 | "inherits": "linux", 175 | "configurePreset": "linux-clang", 176 | "configuration": "Release" 177 | }, 178 | { 179 | "name": "linux-clang-prof", 180 | "displayName": "Linux x64 Clang RelWithDebInfo", 181 | "inherits": "linux", 182 | "configurePreset": "linux-clang", 183 | "configuration": "RelWithDebInfo" 184 | }, 185 | { 186 | "name": "linux-clang-deb", 187 | "displayName": "Linux x64 Clang Debug", 188 | "inherits": "linux", 189 | "configurePreset": "linux-clang", 190 | "configuration": "Debug" 191 | } 192 | ], 193 | "testPresets": [ 194 | { 195 | "name": "windows", 196 | "hidden": true, 197 | "configurePreset": "windows-gcc", 198 | "output": { "outputOnFailure": true }, 199 | "execution": { "noTestsAction": "ignore", "stopOnFailure": true }, 200 | "condition": { 201 | "type": "equals", 202 | "lhs": "${hostSystemName}", 203 | "rhs": "Windows" 204 | } 205 | }, 206 | { 207 | "name": "linux", 208 | "hidden": true, 209 | "configurePreset": "linux-gcc", 210 | "output": { "outputOnFailure": true }, 211 | "execution": { "noTestsAction": "ignore", "stopOnFailure": true }, 212 | "condition": { 213 | "type": "equals", 214 | "lhs": "${hostSystemName}", 215 | "rhs": "Linux" 216 | } 217 | }, 218 | { 219 | "name": "windows-deb", 220 | "displayName": "Windows Debug", 221 | "inherits": "windows", 222 | "configuration": "Debug" 223 | }, 224 | { 225 | "name": "windows-prof", 226 | "displayName": "Windows RelWithDebInfo", 227 | "inherits": "windows", 228 | "configuration": "RelWithDebInfo" 229 | }, 230 | { 231 | "name": "windows-rel", 232 | "displayName": "Windows Release", 233 | "inherits": "windows", 234 | "configuration": "Release" 235 | }, 236 | { 237 | "name": "linux-deb", 238 | "displayName": "Linux Debug", 239 | "inherits": "linux", 240 | "configuration": "Debug" 241 | }, 242 | { 243 | "name": "linux-prof", 244 | "displayName": "Linux RelWithDebInfo", 245 | "inherits": "linux", 246 | "configuration": "RelWithDebInfo" 247 | }, 248 | { 249 | "name": "linux-rel", 250 | "displayName": "Linux Release", 251 | "inherits": "linux", 252 | "configuration": "Release" 253 | } 254 | ] 255 | } 256 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pierre-Antoine Lacaze 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 | -------------------------------------------------------------------------------- /cmake/PalSigslotConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include(CMakeFindDependencyMacro) 4 | find_dependency(Threads) 5 | 6 | include("${CMAKE_CURRENT_LIST_DIR}/PalSigslotTargets.cmake") 7 | -------------------------------------------------------------------------------- /cmake/SigslotUtils.cmake: -------------------------------------------------------------------------------- 1 | # Compiler name 2 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 3 | if (CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC") 4 | set(SIGSLOT_COMPILER_CLANGCL ON) 5 | set(SIGSLOT_COMPILER_NAME "clang-cl") 6 | else() 7 | set(SIGSLOT_COMPILER_CLANG ON) 8 | set(SIGSLOT_COMPILER_NAME "clang") 9 | endif() 10 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") 11 | set(SIGSLOT_COMPILER_GCC ON) 12 | set(SIGSLOT_COMPILER_NAME "gcc") 13 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 14 | set(SIGSLOT_COMPILER_MSVC ON) 15 | set(SIGSLOT_COMPILER_NAME "msvc") 16 | endif() 17 | 18 | if (SIGSLOT_COMPILER_CLANG OR SIGSLOT_COMPILER_GCC) 19 | set(SIGSLOT_COMPILER_CLANG_OR_GCC ON) 20 | endif() 21 | if (SIGSLOT_COMPILER_CLANG OR SIGSLOT_COMPILER_CLANGCL) 22 | set(SIGSLOT_COMPILER_CLANG_OR_CLANGCL ON) 23 | endif() 24 | if (SIGSLOT_COMPILER_CLANG_OR_GCC OR SIGSLOT_COMPILER_CLANGCL) 25 | set(SIGSLOT_COMPILER_CLANG_OR_CLANGCL_OR_GCC ON) 26 | endif() 27 | 28 | add_library(Sigslot_CommonWarnings INTERFACE) 29 | target_compile_options(Sigslot_CommonWarnings INTERFACE 30 | $<$:-Wall;-Wextra> 31 | $<$:-fdiagnostics-color=always;-pipe> 32 | $<$: 33 | -Wno-c++98-compat;-Wno-c++98-compat-pedantic;-Wno-documentation;-Wno-missing-prototypes> 34 | $<$:/W3> 35 | ) 36 | 37 | add_library(Sigslot_ManyWarnings INTERFACE) 38 | target_compile_options(Sigslot_ManyWarnings INTERFACE 39 | $<$: 40 | -Weverything; -Wno-unused-macros;-Wno-newline-eof;-Wno-exit-time-destructors; 41 | -Wno-global-constructors;-Wno-gnu-zero-variadic-macro-arguments;-Wno-documentation; 42 | -Wno-shadow-field-in-constructor;-Wno-missing-prototypes;-Wno-reserved-identifier; 43 | -Wno-documentation-unknown-command;-Wno-ctad-maybe-unsupported;-Wno-c++98-compat; 44 | -Wno-c++98-compat-pedantic;-Wno-weak-vtables;-Wno-padded> 45 | $<$: 46 | -Wcast-qual;-Wconversion-null;-Wmissing-declarations;-Woverlength-strings; 47 | -Wpointer-arith;-Wunused-local-typedefs;-Wunused-result;-Wvarargs;-Wvla; 48 | -Wwrite-strings;-Wconversion;-Wsign-conversion;-Wodr;-Wpedantic;;-pedantic; 49 | -Wcast-align;-Wctor-dtor-privacy;-Wdisabled-optimization;-Wformat=2;-Winit-self; 50 | -Wlogical-op;-Wmissing-include-dirs;-Wold-style-cast;-Woverloaded-virtual; 51 | -Wredundant-decls;-Wno-shadow;-Wsign-promo;-Wstrict-null-sentinel;-Wundef; 52 | -fdiagnostics-show-option;-Wno-return-std-move-in-c++11;-Wno-missing-declarations> 53 | $<$:-fdiagnostics-color=always;-pipe> 54 | $<$:/W4> 55 | ) 56 | 57 | # RTTI 58 | add_library(Sigslot_NoRTTI INTERFACE) 59 | target_compile_options(Sigslot_NoRTTI INTERFACE 60 | $<$:-fno-rtti> 61 | $<$:/GR-> 62 | ) 63 | 64 | # Profiling 65 | add_library(Sigslot_Profiling INTERFACE) 66 | target_compile_options(Sigslot_Profiling INTERFACE 67 | $<$:-g;-fno-omit-frame-pointer> 68 | ) 69 | 70 | # sanitizers 71 | add_library(Sigslot_AddressSanitizer INTERFACE) 72 | target_compile_options(Sigslot_AddressSanitizer INTERFACE 73 | $<$: 74 | -g;-fno-omit-frame-pointer;-fsanitize=address;-fsanitize-address-use-after-scope> 75 | ) 76 | target_link_libraries(Sigslot_AddressSanitizer INTERFACE 77 | $<$:-fsanitize=address> 78 | ) 79 | 80 | add_library(Sigslot_ThreadSanitizer INTERFACE) 81 | target_compile_options(Sigslot_ThreadSanitizer INTERFACE 82 | $<$: 83 | -g;-fno-omit-frame-pointer;-fsanitize=thread> 84 | ) 85 | target_link_libraries(Sigslot_ThreadSanitizer INTERFACE 86 | $<$:-fsanitize=thread> 87 | ) 88 | 89 | add_library(Sigslot_UndefinedSanitizer INTERFACE) 90 | target_compile_options(Sigslot_UndefinedSanitizer INTERFACE 91 | $<$: 92 | -g;-fno-omit-frame-pointer;-fsanitize=undefined> 93 | ) 94 | target_link_libraries(Sigslot_UndefinedSanitizer INTERFACE 95 | $<$:-fsanitize=undefined> 96 | ) 97 | 98 | # Use Libcxx 99 | add_library(Sigslot_Libcxx INTERFACE) 100 | target_compile_options(Sigslot_Libcxx INTERFACE 101 | $<$:-stdlib=libc++> 102 | ) 103 | target_link_libraries(Sigslot_Libcxx INTERFACE 104 | $<$:-stdlib=libc++;-rtlib=compiler-rt> 105 | ) 106 | 107 | option(SIGSLOT_ENABLE_COMMON_WARNINGS "Enable common compiler flags" ON) 108 | option(SIGSLOT_ENABLE_MANY_WARNINGS "Enable most compiler flags" OFF) 109 | option(SIGSLOT_DISABLE_RTTI "Disable Runtime Type Information" OFF) 110 | option(SIGSLOT_ENABLE_LTO "Enable link time optimization (release only)" OFF) 111 | option(SIGSLOT_ENABLE_LIBCXX "Use libcxx with clang" OFF) 112 | option(SIGSLOT_ENABLE_PROFILING "Add compile flags to help with profiling" OFF) 113 | option(SIGSLOT_SANITIZE_ADDRESS "Compile with address sanitizer support" OFF) 114 | option(SIGSLOT_SANITIZE_THREADS "Compile with thread sanitizer support" OFF) 115 | option(SIGSLOT_SANITIZE_UNDEFINED "Compile with undefined sanitizer support" OFF) 116 | 117 | # common properties 118 | function(sigslot_set_properties target scope) 119 | target_compile_features(${target} ${scope} cxx_std_14) 120 | set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF) 121 | 122 | # account for options 123 | target_link_libraries(${target} ${scope} 124 | $<$:Sigslot_CommonWarnings> 125 | $<$:Sigslot_ManyWarnings> 126 | $<$:Sigslot_NoRTTI> 127 | $<$:Sigslot_Libcxx> 128 | $<$:Sigslot_Profiling> 129 | $<$:Sigslot_AddressSanitizer> 130 | $<$:Sigslot_ThreadSanitizer> 131 | $<$:Sigslot_UndefinedSanitizer> 132 | ) 133 | 134 | if (SIGSLOT_ENABLE_LTO) 135 | set_target_properties(${target} PROPERTIES INTERFACE_INTERPROCEDURAL_OPTIMIZATION ON) 136 | endif() 137 | endfunction() 138 | -------------------------------------------------------------------------------- /example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_target(sigslot-examples COMMENT "Build all the examples.") 2 | 3 | macro(pal_create_example target ut) 4 | add_executable(${target} EXCLUDE_FROM_ALL "${ut}") 5 | target_link_libraries(${target} PRIVATE Pal::Sigslot) 6 | target_compile_options(${target} PRIVATE $<$:/bigobj>) 7 | target_compile_definitions(${target} PRIVATE $<$:_SCL_SECURE_NO_WARNINGS>) 8 | add_dependencies(sigslot-examples ${target}) 9 | sigslot_set_properties(${target} PRIVATE) 10 | endmacro() 11 | 12 | file(GLOB_RECURSE UNIT_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cpp") 13 | foreach(ex IN LISTS UNIT_TESTS) 14 | string(REPLACE ".cpp" "" target ${ex}) 15 | string(REGEX REPLACE "/" "." target ${target}) 16 | set(target sigslot-example-${target}) 17 | 18 | if (target MATCHES "boost") 19 | if (TARGET Boost::system) 20 | pal_create_example(${target} "${ex}") 21 | target_link_libraries(${target} PRIVATE Boost::system) 22 | endif() 23 | elseif (target MATCHES "qt") 24 | if (TARGET Qt5::Core) 25 | pal_create_example(${target} "${ex}") 26 | target_link_libraries(${target} PRIVATE Qt5::Core) 27 | set_target_properties(${target} PROPERTIES AUTOMOC ON) 28 | endif() 29 | else() 30 | pal_create_example(${target} "${ex}") 31 | endif() 32 | endforeach() 33 | -------------------------------------------------------------------------------- /example/arguments.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct foo { 6 | // Notice how we accept a double as first argument here 7 | // This is fine because float is convertible to double 8 | void bar(float d, int i, bool b, std::string &s) { 9 | s = b ? std::to_string(i) : std::to_string(d); 10 | } 11 | }; 12 | 13 | // Function objects can cope with default arguments and overloading. 14 | // It does not work with static and member functions. 15 | struct obj { 16 | void operator()(float, int, bool, std::string &, int = 0) { 17 | std::cout << "I was here\n"; 18 | } 19 | 20 | void operator()() {} 21 | }; 22 | 23 | // A generic function object that deals with any input argument 24 | struct printer { 25 | template 26 | void operator()(T a, Ts && ...args) const { 27 | std::cout << a; 28 | (void)std::initializer_list{ 29 | ((void)(std::cout << " " << std::forward(args)), 1)... 30 | }; 31 | std::cout << "\n"; 32 | } 33 | }; 34 | 35 | int main() { 36 | // declare a signal with float, int, bool and string& arguments 37 | sigslot::signal sig; 38 | 39 | // a Generic lambda that prints its arguments to stdout 40 | auto lambda_printer = [] (auto a, auto && ...args) { 41 | std::cout << a; 42 | (void)std::initializer_list{ 43 | ((void)(std::cout << " " << args), 1)... 44 | }; 45 | std::cout << "\n"; 46 | }; 47 | 48 | // connect the slots 49 | foo ff; 50 | sig.connect(printer()); 51 | sig.connect(&foo::bar, &ff); 52 | sig.connect(lambda_printer); 53 | sig.connect(obj()); 54 | 55 | float f = 1.f; 56 | short i = 2; 57 | std::string s = "0"; 58 | 59 | // emit a signal 60 | sig(f, i, false, s); 61 | sig(f, i, true, s); 62 | 63 | return 0; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /example/basic.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void f() { std::cout << "free function\n"; } 5 | 6 | struct s { 7 | void m() { std::cout << "member function\n"; } 8 | static void sm() { std::cout << "static member function\n"; } 9 | }; 10 | 11 | struct o { 12 | void operator()() { std::cout << "function object\n"; } 13 | }; 14 | 15 | void basic_member_connect() { 16 | s d; 17 | auto lambda = []() { std::cout << "lambda\n"; }; 18 | 19 | // declare a signal instance with no arguments 20 | sigslot::signal<> sig; 21 | 22 | // sigslot::signal will connect to any callable provided it has compatible 23 | // arguments. Here are diverse examples 24 | sig.connect(f); 25 | sig.connect(&s::m, &d); 26 | sig.connect(&s::sm); 27 | sig.connect(o()); 28 | sig.connect(lambda); 29 | 30 | // Avoid hitting bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68071 31 | // on old GCC compilers 32 | #if defined(__clang__) || GCC_VERSION > 70300 33 | auto gen_lambda = [](auto && ... /*a*/) { std::cout << "generic lambda\n"; }; 34 | sig.connect(gen_lambda); 35 | #endif 36 | 37 | // emit a signal 38 | sig(); 39 | } 40 | 41 | void basic_freestanding_connect() { 42 | s d; 43 | auto lambda = []() { std::cout << "lambda\n"; }; 44 | 45 | // declare a signal instance with no arguments 46 | sigslot::signal<> sig; 47 | sigslot::signal<> sig2; 48 | 49 | // sigslot::signal will connect to any callable provided it has compatible 50 | // arguments. Here are diverse examples 51 | sigslot::connect(sig, f); 52 | sigslot::connect(sig, &s::m, &d); 53 | sigslot::connect(sig, &s::sm); 54 | sigslot::connect(sig, o()); 55 | sigslot::connect(sig, lambda); 56 | sigslot::connect(sig, sig2); 57 | 58 | sigslot::connect(sig2, [] { std::cout << "Signal chaining too\n"; }); 59 | 60 | // Avoid hitting bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68071 61 | // on old GCC compilers 62 | #if defined(__clang__) || GCC_VERSION > 70300 63 | auto gen_lambda = [](auto && ... /*a*/) { std::cout << "generic lambda\n"; }; 64 | sigslot::connect(sig, gen_lambda); 65 | #endif 66 | 67 | // emit a signal 68 | sig(); 69 | } 70 | 71 | int main() { 72 | basic_member_connect(); 73 | basic_freestanding_connect(); 74 | return 0; 75 | } 76 | -------------------------------------------------------------------------------- /example/connection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static int i = 0; 6 | 7 | void f() { 8 | i += 1; 9 | std::cout << "i == " << i << std::endl; 10 | } 11 | 12 | int main() { 13 | sigslot::signal<> sig; 14 | 15 | // keep a sigslot::connection object 16 | auto c1 = sig.connect(f); 17 | 18 | // disconnection 19 | sig(); // i == 1 20 | c1.disconnect(); 21 | sig(); // i == 1 22 | 23 | // scope based disconnection 24 | { 25 | sigslot::scoped_connection sc = sig.connect(f); 26 | sig(); // i == 2 27 | } 28 | 29 | sig(); // i == 2; 30 | 31 | 32 | // connection blocking 33 | auto c2 = sig.connect(f); 34 | sig(); // i == 3 35 | c2.block(); 36 | sig(); // i == 3 37 | c2.unblock(); 38 | sig(); // i == 4 39 | 40 | c2.disconnect(); 41 | 42 | // extended connection 43 | auto f2 = [](auto &con) { 44 | f(); // execute one 45 | con.disconnect(); // then disconnects 46 | }; 47 | 48 | sig.connect_extended(f2); 49 | sig(); // i == 5 50 | sig(); // i == 5 because f2 was disconnected 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /example/default-arguments.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define ADAPT(func) \ 4 | [=](auto && ...a) { (func)(std::forward(a)...); } 5 | 6 | void foo(int &i, int b = 1) { 7 | i += b; 8 | } 9 | 10 | int main() { 11 | int i = 0; 12 | 13 | // fine, all the arguments are handled 14 | sigslot::signal sig1; 15 | sig1.connect(foo); 16 | sig1(i, 2); 17 | 18 | // must wrap in an adapter 19 | i = 0; 20 | sigslot::signal sig2; 21 | sig2.connect(ADAPT(foo)); 22 | sig2(i); 23 | 24 | return 0; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /example/disconnection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace { 6 | static int ti = 0; 7 | } // anonymous namespace 8 | 9 | void f1() { ti += 1; } 10 | void f2() { ti += 1; } 11 | 12 | struct s { 13 | virtual ~s() = default; 14 | void m1() { ti += 1; } 15 | void m2() { ti += 1; } 16 | void m3() { ti += 1; } 17 | 18 | virtual void v() = 0; 19 | }; 20 | 21 | struct b { 22 | virtual ~b() = default; 23 | }; 24 | 25 | struct d : s, b { 26 | void v() override { ti += 1; } 27 | }; 28 | 29 | struct o { 30 | void operator()() { ti += 1; } 31 | }; 32 | 33 | int main() { 34 | sigslot::signal<> sig; 35 | d s1; 36 | auto s2 = std::make_shared(); 37 | 38 | auto lbd = [&] { ti += 1; }; 39 | 40 | sig.connect(f1); // #1 41 | sig.connect(f2); // #2 42 | sig.connect(&s::m1, &s1); // #3 43 | sig.connect(&s::m2, &s1); // #4 44 | sig.connect(&s::m3, &s1); // #5 45 | sig.connect(&s::m1, s2); // #6 46 | sig.connect(&s::m2, s2); // #7 47 | sig.connect(&s::v, s2); // #8 48 | sig.connect(o{}); // #0 49 | sig.connect(lbd); // #10 50 | 51 | sig(); // i == 10 52 | std::cout << "i = " << ti << std::endl; 53 | 54 | sig.disconnect(f2); // #2 is removed 55 | 56 | #ifdef SIGSLOT_RTTI_ENABLED 57 | sig.disconnect(&s::m1); // #3 and #6 are removed 58 | sig.disconnect(o{}); // #9 and is removed 59 | // sig.disconnect(&o::operator()); // same as the above, more efficient 60 | sig.disconnect(lbd); // #10 and is removed 61 | sig.disconnect(&s::v); // #8 and is removed 62 | #endif 63 | 64 | sig.disconnect(s2); // #7 is removed 65 | sig.disconnect(&s::m3, &s1); // #5 is removed, not #4 66 | 67 | sig(); // i == 12 with RTTI, 15 without RTTI 68 | std::cout << "i = " << ti << std::endl; 69 | 70 | sig.disconnect_all(); // remove all remaining slots 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /example/lambdas.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* 5 | * This example is meant to test compilation time as well as object size when a 6 | * lot of unique callables get connected to a signal. 7 | * This example motivated the introduction of a SOCUTE_REDUCE_COMPILE_TIME cmake option. 8 | */ 9 | 10 | #define LAMBDA4() sig.connect([](int) { puts("lambda\n"); }); 11 | #define LAMBDA3() LAMBDA4() LAMBDA4() LAMBDA4() LAMBDA4() LAMBDA4() 12 | #define LAMBDA2() LAMBDA3() LAMBDA3() LAMBDA3() LAMBDA3() LAMBDA3() 13 | #define LAMBDA1() LAMBDA2() LAMBDA2() LAMBDA2() LAMBDA2() LAMBDA2() 14 | #define LAMBDAS() LAMBDA1() LAMBDA1() LAMBDA1() LAMBDA1() LAMBDA1() 15 | 16 | int main() { 17 | sigslot::signal sig; 18 | 19 | // connect a bunch of lambdas 20 | LAMBDAS() 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /example/overloads.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | template 4 | constexpr auto overload(void (C::*ptr)(Args...)) { 5 | return ptr; 6 | } 7 | 8 | template 9 | constexpr auto overload(void (*ptr)(Args...)) { 10 | return ptr; 11 | } 12 | 13 | struct obj { 14 | void operator()(int) const {} 15 | void operator()() {} 16 | }; 17 | 18 | struct foo { 19 | void bar(int) {} 20 | void bar() {} 21 | 22 | static void baz(int) {} 23 | static void baz() {} 24 | }; 25 | 26 | void moo(int) {} 27 | void moo() {} 28 | 29 | int main() { 30 | sigslot::signal sig; 31 | 32 | // connect the slots, casting to the right overload if necessary 33 | foo ff; 34 | sig.connect(overload(&foo::bar), &ff); 35 | sig.connect(overload(&foo::baz)); 36 | sig.connect(overload(&moo)); 37 | sig.connect(obj()); 38 | 39 | sig(0); 40 | 41 | return 0; 42 | } 43 | 44 | -------------------------------------------------------------------------------- /example/slot-group.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | static auto printer(std::string pos) { 6 | return [pos=std::move(pos)] (std::string s, int i) { 7 | std::cout << pos << " to print " << s << " and " << i << std::endl; 8 | }; 9 | } 10 | 11 | int main() { 12 | sigslot::signal sig; 13 | 14 | sig.connect(printer("Second"), 1); 15 | sig.connect(printer("Last"), std::numeric_limits::max()); 16 | sig.connect(printer("Third"), 2); 17 | sig.connect(printer("First"), 0); 18 | sig("bar", 1); 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /include/sigslot/adapter/boost.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | /** 6 | * Bring ADL conversion for boost smart pointers 7 | */ 8 | 9 | namespace boost { 10 | 11 | template 12 | weak_ptr to_weak(shared_ptr p) { 13 | return {p}; 14 | } 15 | 16 | template 17 | weak_ptr to_weak(weak_ptr p) { 18 | return p; 19 | } 20 | 21 | } // namespace boost 22 | 23 | -------------------------------------------------------------------------------- /include/sigslot/adapter/qt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * Definition of a few adapters that allow object life tracking for QObject, 10 | * QWeakPointer and QSharedPointer objects, when connected to signals. 11 | */ 12 | 13 | namespace sigslot { 14 | namespace detail { 15 | 16 | // a weak pointer adapter to allow QObject life tracking 17 | template 18 | struct qpointer_adapter { 19 | qpointer_adapter(T *o) noexcept 20 | : m_ptr{o} 21 | {} 22 | 23 | void reset() noexcept { 24 | m_ptr.clear(); 25 | } 26 | 27 | bool expired() const noexcept { 28 | return m_ptr.isNull(); 29 | } 30 | 31 | // Warning: very unsafe because QPointer does not provide weak pointer semantics 32 | // In a multithreaded context, m_ptr may very well be destroyed in the lapse of 33 | // time between expired() and data() (and also while data() is being used). 34 | T* lock() const noexcept { 35 | return expired() ? nullptr : m_ptr.data(); 36 | } 37 | 38 | private: 39 | QPointer m_ptr; 40 | }; 41 | 42 | // a wrapper that exposes the right concepts for QWeakPointer 43 | template 44 | struct qweakpointer_adapter { 45 | qweakpointer_adapter(QWeakPointer o) noexcept 46 | : m_ptr{std::move(o)} 47 | {} 48 | 49 | void reset() noexcept { 50 | m_ptr.clear(); 51 | } 52 | 53 | bool expired() const noexcept { 54 | return m_ptr.isNull(); 55 | } 56 | 57 | QSharedPointer lock() const noexcept { 58 | return m_ptr.lock(); 59 | } 60 | 61 | private: 62 | QWeakPointer m_ptr; 63 | }; 64 | 65 | } // namespace detail 66 | } // namespace sigslot 67 | 68 | 69 | QT_BEGIN_NAMESPACE 70 | 71 | template 72 | std::enable_if_t::value, sigslot::detail::qpointer_adapter> 73 | to_weak(T *p) { 74 | return {p}; 75 | } 76 | 77 | template 78 | sigslot::detail::qweakpointer_adapter to_weak(QWeakPointer p) { 79 | return {p}; 80 | } 81 | 82 | template 83 | sigslot::detail::qweakpointer_adapter to_weak(QSharedPointer p) { 84 | return {p}; 85 | } 86 | 87 | QT_END_NAMESPACE 88 | 89 | -------------------------------------------------------------------------------- /include/sigslot/signal.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #if defined(__GXX_RTTI) || defined(__cpp_rtti) || defined(_CPPRTTI) 12 | #define SIGSLOT_RTTI_ENABLED 1 13 | #include 14 | #endif 15 | 16 | namespace sigslot { 17 | 18 | template 19 | class signal_base; 20 | 21 | namespace detail { 22 | 23 | // Used to detect an object of observer type 24 | struct observer_type {}; 25 | 26 | } // namespace detail 27 | 28 | namespace trait { 29 | 30 | /// represent a list of types 31 | template struct typelist {}; 32 | 33 | /** 34 | * Pointers that can be converted to a weak pointer concept for tracking 35 | * purpose must implement the to_weak() function in order to make use of 36 | * ADL to convert that type and make it usable 37 | */ 38 | 39 | template 40 | std::weak_ptr to_weak(std::weak_ptr w) { 41 | return w; 42 | } 43 | 44 | template 45 | std::weak_ptr to_weak(std::shared_ptr s) { 46 | return s; 47 | } 48 | 49 | // tools 50 | namespace detail { 51 | 52 | template 53 | struct voider { using type = void; }; 54 | 55 | // void_t from c++17 56 | template 57 | using void_t = typename detail::voider::type; 58 | 59 | template 60 | struct has_call_operator : std::false_type {}; 61 | 62 | template 63 | struct has_call_operator::type::operator())>> 64 | : std::true_type {}; 65 | 66 | template 67 | struct is_callable : std::false_type {}; 68 | 69 | template 70 | struct is_callable, 71 | void_t()).*std::declval())(std::declval()...))>> 72 | : std::true_type {}; 73 | 74 | template 75 | struct is_callable, 76 | void_t()(std::declval()...))>> 77 | : std::true_type {}; 78 | 79 | 80 | template 81 | struct is_weak_ptr : std::false_type {}; 82 | 83 | template 84 | struct is_weak_ptr().expired()), 85 | decltype(std::declval().lock()), 86 | decltype(std::declval().reset())>> 87 | : std::true_type {}; 88 | 89 | template 90 | struct is_weak_ptr_compatible : std::false_type {}; 91 | 92 | template 93 | struct is_weak_ptr_compatible()))>> 94 | : is_weak_ptr()))> {}; 95 | 96 | template 97 | struct is_signal : std::false_type {}; 98 | 99 | template 100 | struct is_signal> 101 | : std::true_type {}; 102 | 103 | } // namespace detail 104 | 105 | static constexpr bool with_rtti = 106 | #ifdef SIGSLOT_RTTI_ENABLED 107 | true; 108 | #else 109 | false; 110 | #endif 111 | 112 | /// determine if a pointer is convertible into a "weak" pointer 113 | template 114 | constexpr bool is_weak_ptr_compatible_v = detail::is_weak_ptr_compatible>::value; 115 | 116 | /// determine if a type T (Callable or Pmf) is callable with supplied arguments 117 | template 118 | constexpr bool is_callable_v = detail::is_callable::value; 119 | 120 | template 121 | constexpr bool is_weak_ptr_v = detail::is_weak_ptr::value; 122 | 123 | template 124 | constexpr bool has_call_operator_v = detail::has_call_operator::value; 125 | 126 | template 127 | constexpr bool is_pointer_v = std::is_pointer::value; 128 | 129 | template 130 | constexpr bool is_func_v = std::is_function::value; 131 | 132 | template 133 | constexpr bool is_pmf_v = std::is_member_function_pointer::value; 134 | 135 | template 136 | constexpr bool is_observer_v = std::is_base_of<::sigslot::detail::observer_type, 137 | std::remove_pointer_t>>::value; 138 | 139 | template 140 | constexpr bool is_signal_v = detail::is_signal::value; 141 | 142 | } // namespace trait 143 | 144 | /** 145 | * A group_id is used to identify a group of slots 146 | */ 147 | using group_id = std::int32_t; 148 | 149 | namespace detail { 150 | 151 | /** 152 | * The following function_traits and object_pointer series of templates are 153 | * used to circumvent the type-erasing that takes place in the slot_base 154 | * implementations. They are used to compare the stored functions and objects 155 | * with another one for disconnection purpose. 156 | */ 157 | 158 | /* 159 | * Function pointers and member function pointers size differ from compiler to 160 | * compiler, and for virtual members compared to non virtual members. On some 161 | * compilers, multiple inheritance has an impact too. Hence, we form an union 162 | * big enough to store any kind of function pointer. 163 | */ 164 | namespace mock { 165 | 166 | struct a { virtual ~a() = default; void f(); virtual void g(); static void h(); }; 167 | struct b { virtual ~b() = default; void f(); virtual void g(); }; 168 | struct c : a, b { void f(); void g() override; }; 169 | struct d : virtual a { void g() override; }; 170 | 171 | union fun_types { 172 | decltype(&d::g) dm; 173 | decltype(&c::g) mm; 174 | decltype(&c::g) mvm; 175 | decltype(&a::f) m; 176 | decltype(&a::g) vm; 177 | decltype(&a::h) s; 178 | void (*f)(); 179 | void *o; 180 | }; 181 | 182 | } // namespace mock 183 | 184 | /* 185 | * This struct is used to store function pointers. 186 | * This is needed for slot disconnection by function pointer. 187 | * It assumes the underlying implementation to be trivially copiable. 188 | */ 189 | struct func_ptr { 190 | func_ptr() 191 | : sz{0} 192 | { 193 | std::uninitialized_fill(std::begin(data), std::end(data), '\0'); 194 | } 195 | 196 | template 197 | void store(const T &t) { 198 | const auto *b = reinterpret_cast(&t); 199 | sz = sizeof(T); 200 | std::memcpy(data, b, sz); 201 | } 202 | 203 | template 204 | const T* as() const { 205 | if (sizeof(T) != sz) { 206 | return nullptr; 207 | } 208 | return reinterpret_cast(data); 209 | } 210 | 211 | private: 212 | alignas(sizeof(mock::fun_types)) char data[sizeof(mock::fun_types)]; 213 | size_t sz; 214 | }; 215 | 216 | 217 | template 218 | struct function_traits { 219 | static void ptr(const T &/*t*/, func_ptr &/*d*/) { 220 | } 221 | 222 | static bool eq(const T &/*t*/, const func_ptr &/*d*/) { 223 | return false; 224 | } 225 | 226 | static constexpr bool is_disconnectable = false; 227 | static constexpr bool must_check_object = true; 228 | }; 229 | 230 | template 231 | struct function_traits>> { 232 | static void ptr(T &t, func_ptr &d) { 233 | d.store(&t); 234 | } 235 | 236 | static bool eq(T &t, const func_ptr &d) { 237 | const auto *r = d.as(); 238 | return r && *r == &t; 239 | } 240 | 241 | static constexpr bool is_disconnectable = true; 242 | static constexpr bool must_check_object = false; 243 | }; 244 | 245 | template 246 | struct function_traits>> { 247 | static void ptr(T *t, func_ptr &d) { 248 | function_traits::ptr(*t, d); 249 | } 250 | 251 | static bool eq(T *t, const func_ptr &d) { 252 | return function_traits::eq(*t, d); 253 | } 254 | 255 | static constexpr bool is_disconnectable = true; 256 | static constexpr bool must_check_object = false; 257 | }; 258 | 259 | template 260 | struct function_traits>> { 261 | static void ptr(T t, func_ptr &d) { 262 | d.store(t); 263 | } 264 | 265 | static bool eq(T t, const func_ptr &d) { 266 | const auto *r = d.as(); 267 | return r && *r == t; 268 | } 269 | 270 | static constexpr bool is_disconnectable = trait::with_rtti; 271 | static constexpr bool must_check_object = true; 272 | }; 273 | 274 | // for function objects, the assumption is that we are looking for the call operator 275 | template 276 | struct function_traits>> { 277 | using call_type = decltype(&std::remove_reference::type::operator()); 278 | 279 | static void ptr(const T &/*t*/, func_ptr &d) { 280 | function_traits::ptr(&T::operator(), d); 281 | } 282 | 283 | static bool eq(const T &/*t*/, const func_ptr &d) { 284 | return function_traits::eq(&T::operator(), d); 285 | } 286 | 287 | static constexpr bool is_disconnectable = function_traits::is_disconnectable; 288 | static constexpr bool must_check_object = function_traits::must_check_object; 289 | }; 290 | 291 | template 292 | func_ptr get_function_ptr(const T &t) { 293 | func_ptr d; 294 | function_traits>::ptr(t, d); 295 | return d; 296 | } 297 | 298 | template 299 | bool eq_function_ptr(const T& t, const func_ptr &d) { 300 | return function_traits>::eq(t, d); 301 | } 302 | 303 | /* 304 | * obj_ptr is used to store a pointer to an object. 305 | * The object_pointer traits are needed to handle trackable objects correctly, 306 | * as they are likely to not be pointers. 307 | */ 308 | using obj_ptr = const void*; 309 | 310 | template 311 | obj_ptr get_object_ptr(const T &t); 312 | 313 | template 314 | struct object_pointer { 315 | static obj_ptr get(const T&) { 316 | return nullptr; 317 | } 318 | }; 319 | 320 | template 321 | struct object_pointer>> { 322 | static obj_ptr get(const T *t) { 323 | return reinterpret_cast(t); 324 | } 325 | }; 326 | 327 | template 328 | struct object_pointer>> { 329 | static obj_ptr get(const T &t) { 330 | auto p = t.lock(); 331 | return get_object_ptr(p); 332 | } 333 | }; 334 | 335 | template 336 | struct object_pointer && 337 | !trait::is_weak_ptr_v && 338 | trait::is_weak_ptr_compatible_v>> 339 | { 340 | static obj_ptr get(const T &t) { 341 | return t ? reinterpret_cast(t.get()) : nullptr; 342 | } 343 | }; 344 | 345 | template 346 | obj_ptr get_object_ptr(const T &t) { 347 | return object_pointer::get(t); 348 | } 349 | 350 | 351 | // noop mutex for thread-unsafe use 352 | struct null_mutex { 353 | null_mutex() noexcept = default; 354 | ~null_mutex() noexcept = default; 355 | null_mutex(const null_mutex &) = delete; 356 | null_mutex& operator=(const null_mutex &) = delete; 357 | null_mutex(null_mutex &&) = delete; 358 | null_mutex& operator=(null_mutex &&) = delete; 359 | 360 | inline bool try_lock() noexcept { return true; } 361 | inline void lock() noexcept {} 362 | inline void unlock() noexcept {} 363 | }; 364 | 365 | /** 366 | * A spin mutex that yields, mostly for use in benchmarks and scenarii that invoke 367 | * slots at a very high pace. 368 | * One should almost always prefer a standard mutex over this. 369 | */ 370 | struct spin_mutex { 371 | spin_mutex() noexcept = default; 372 | ~spin_mutex() noexcept = default; 373 | spin_mutex(spin_mutex const&) = delete; 374 | spin_mutex& operator=(const spin_mutex &) = delete; 375 | spin_mutex(spin_mutex &&) = delete; 376 | spin_mutex& operator=(spin_mutex &&) = delete; 377 | 378 | void lock() noexcept { 379 | while (true) { 380 | while (!state.load(std::memory_order_relaxed)) { 381 | std::this_thread::yield(); 382 | } 383 | 384 | if (try_lock()) { 385 | break; 386 | } 387 | } 388 | } 389 | 390 | bool try_lock() noexcept { 391 | return state.exchange(false, std::memory_order_acquire); 392 | } 393 | 394 | void unlock() noexcept { 395 | state.store(true, std::memory_order_release); 396 | } 397 | 398 | private: 399 | std::atomic state {true}; 400 | }; 401 | 402 | /** 403 | * A simple copy on write container that will be used to improve slot lists 404 | * access efficiency in a multithreaded context. 405 | */ 406 | template 407 | class copy_on_write { 408 | struct payload { 409 | payload() = default; 410 | 411 | template 412 | explicit payload(Args && ...args) 413 | : value(std::forward(args)...) 414 | {} 415 | 416 | std::atomic count{1}; 417 | T value; 418 | }; 419 | 420 | public: 421 | using element_type = T; 422 | 423 | copy_on_write() 424 | : m_data(new payload) 425 | {} 426 | 427 | template 428 | explicit copy_on_write(U && x, std::enable_if_t, 429 | copy_on_write>::value>* = nullptr) 430 | : m_data(new payload(std::forward(x))) 431 | {} 432 | 433 | copy_on_write(const copy_on_write &x) noexcept 434 | : m_data(x.m_data) 435 | { 436 | ++m_data->count; 437 | } 438 | 439 | copy_on_write(copy_on_write && x) noexcept 440 | : m_data(x.m_data) 441 | { 442 | x.m_data = nullptr; 443 | } 444 | 445 | ~copy_on_write() { 446 | if (m_data && (--m_data->count == 0)) { 447 | delete m_data; 448 | } 449 | } 450 | 451 | copy_on_write& operator=(const copy_on_write &x) noexcept { 452 | if (&x != this) { 453 | *this = copy_on_write(x); 454 | } 455 | return *this; 456 | } 457 | 458 | copy_on_write& operator=(copy_on_write && x) noexcept { 459 | auto tmp = std::move(x); 460 | swap(*this, tmp); 461 | return *this; 462 | } 463 | 464 | element_type& write() { 465 | if (!unique()) { 466 | *this = copy_on_write(read()); 467 | } 468 | return m_data->value; 469 | } 470 | 471 | const element_type& read() const noexcept { 472 | return m_data->value; 473 | } 474 | 475 | friend inline void swap(copy_on_write &x, copy_on_write &y) noexcept { 476 | using std::swap; 477 | swap(x.m_data, y.m_data); 478 | } 479 | 480 | private: 481 | bool unique() const noexcept { 482 | return m_data->count == 1; 483 | } 484 | 485 | private: 486 | payload *m_data; 487 | }; 488 | 489 | /** 490 | * Specializations for thread-safe code path 491 | */ 492 | template 493 | const T& cow_read(const T &v) { 494 | return v; 495 | } 496 | 497 | template 498 | const T& cow_read(copy_on_write &v) { 499 | return v.read(); 500 | } 501 | 502 | template 503 | T& cow_write(T &v) { 504 | return v; 505 | } 506 | 507 | template 508 | T& cow_write(copy_on_write &v) { 509 | return v.write(); 510 | } 511 | 512 | /** 513 | * std::make_shared instantiates a lot a templates, and makes both compilation time 514 | * and executable size far bigger than they need to be. We offer a make_shared 515 | * equivalent that will avoid most instantiations with the following tradeoffs: 516 | * - Not exception safe, 517 | * - Allocates a separate control block, and will thus make the code slower. 518 | */ 519 | #ifdef SIGSLOT_REDUCE_COMPILE_TIME 520 | template 521 | inline std::shared_ptr make_shared(Arg && ... arg) { 522 | return std::shared_ptr(static_cast(new D(std::forward(arg)...))); 523 | } 524 | #else 525 | template 526 | inline std::shared_ptr make_shared(Arg && ... arg) { 527 | return std::static_pointer_cast(std::make_shared(std::forward(arg)...)); 528 | } 529 | #endif 530 | 531 | 532 | // Adapt a signal into a cheap function object, for easy signal chaining 533 | template 534 | struct signal_wrapper { 535 | template 536 | void operator()(U && ...u) { 537 | (*m_sig)(std::forward(u)...); 538 | } 539 | 540 | SigT *m_sig{}; 541 | }; 542 | 543 | 544 | /* slot_state holds slot type independent state, to be used to interact with 545 | * slots indirectly through connection and scoped_connection objects. 546 | */ 547 | class slot_state { 548 | public: 549 | constexpr slot_state(group_id gid) noexcept 550 | : m_index(0) 551 | , m_group(gid) 552 | , m_connected(true) 553 | , m_blocked(false) 554 | {} 555 | 556 | virtual ~slot_state() = default; 557 | 558 | virtual bool connected() const noexcept { return m_connected; } 559 | 560 | bool disconnect() noexcept { 561 | bool ret = m_connected.exchange(false); 562 | if (ret) { 563 | do_disconnect(); 564 | } 565 | return ret; 566 | } 567 | 568 | bool blocked() const noexcept { return m_blocked.load(); } 569 | void block() noexcept { m_blocked.store(true); } 570 | void unblock() noexcept { m_blocked.store(false); } 571 | 572 | protected: 573 | virtual void do_disconnect() {} 574 | 575 | auto index() const { 576 | return m_index; 577 | } 578 | 579 | auto& index() { 580 | return m_index; 581 | } 582 | 583 | group_id group() const { 584 | return m_group; 585 | } 586 | 587 | private: 588 | template 589 | friend class ::sigslot::signal_base; 590 | 591 | std::size_t m_index; // index into the array of slot pointers inside the signal 592 | const group_id m_group; // slot group this slot belongs to 593 | std::atomic m_connected; 594 | std::atomic m_blocked; 595 | }; 596 | 597 | } // namespace detail 598 | 599 | /** 600 | * connection_blocker is a RAII object that blocks a connection until destruction 601 | */ 602 | class connection_blocker { 603 | public: 604 | connection_blocker() = default; 605 | ~connection_blocker() noexcept { release(); } 606 | 607 | connection_blocker(const connection_blocker &) = delete; 608 | connection_blocker & operator=(const connection_blocker &) = delete; 609 | 610 | connection_blocker(connection_blocker && o) noexcept 611 | : m_state{std::move(o.m_state)} 612 | {} 613 | 614 | connection_blocker & operator=(connection_blocker && o) noexcept { 615 | release(); 616 | m_state.swap(o.m_state); 617 | return *this; 618 | } 619 | 620 | private: 621 | friend class connection; 622 | explicit connection_blocker(std::weak_ptr s) noexcept 623 | : m_state{std::move(s)} 624 | { 625 | if (auto d = m_state.lock()) { 626 | d->block(); 627 | } 628 | } 629 | 630 | void release() noexcept { 631 | if (auto d = m_state.lock()) { 632 | d->unblock(); 633 | } 634 | } 635 | 636 | private: 637 | std::weak_ptr m_state; 638 | }; 639 | 640 | 641 | /** 642 | * A connection object allows interaction with an ongoing slot connection 643 | * 644 | * It allows common actions such as connection blocking and disconnection. 645 | * Note that connection is not a RAII object, one does not need to hold one 646 | * such object to keep the signal-slot connection alive. 647 | */ 648 | class connection { 649 | public: 650 | connection() = default; 651 | virtual ~connection() = default; 652 | 653 | connection(const connection &) noexcept = default; 654 | connection & operator=(const connection &) noexcept = default; 655 | connection(connection &&) noexcept = default; 656 | connection & operator=(connection &&) noexcept = default; 657 | 658 | bool valid() const noexcept { 659 | return !m_state.expired(); 660 | } 661 | 662 | bool connected() const noexcept { 663 | const auto d = m_state.lock(); 664 | return d && d->connected(); 665 | } 666 | 667 | bool disconnect() noexcept { 668 | auto d = m_state.lock(); 669 | return d && d->disconnect(); 670 | } 671 | 672 | bool blocked() const noexcept { 673 | const auto d = m_state.lock(); 674 | return d && d->blocked(); 675 | } 676 | 677 | void block() noexcept { 678 | if (auto d = m_state.lock()) { 679 | d->block(); 680 | } 681 | } 682 | 683 | void unblock() noexcept { 684 | if (auto d = m_state.lock()) { 685 | d->unblock(); 686 | } 687 | } 688 | 689 | connection_blocker blocker() const noexcept { 690 | return connection_blocker{m_state}; 691 | } 692 | 693 | protected: 694 | template friend class signal_base; 695 | explicit connection(std::weak_ptr s) noexcept 696 | : m_state{std::move(s)} 697 | {} 698 | 699 | protected: 700 | std::weak_ptr m_state; 701 | }; 702 | 703 | /** 704 | * scoped_connection is a RAII version of connection 705 | * It disconnects the slot from the signal upon destruction. 706 | */ 707 | class scoped_connection final : public connection { 708 | public: 709 | scoped_connection() = default; 710 | ~scoped_connection() override { 711 | disconnect(); 712 | } 713 | 714 | /*implicit*/ scoped_connection(const connection &c) noexcept : connection(c) {} 715 | /*implicit*/ scoped_connection(connection &&c) noexcept : connection(std::move(c)) {} 716 | 717 | scoped_connection(const scoped_connection &) noexcept = delete; 718 | scoped_connection & operator=(const scoped_connection &) noexcept = delete; 719 | 720 | scoped_connection(scoped_connection && o) noexcept 721 | : connection{std::move(o.m_state)} 722 | {} 723 | 724 | scoped_connection & operator=(scoped_connection && o) noexcept { 725 | disconnect(); 726 | m_state.swap(o.m_state); 727 | return *this; 728 | } 729 | 730 | private: 731 | template friend class signal_base; 732 | explicit scoped_connection(std::weak_ptr s) noexcept 733 | : connection{std::move(s)} 734 | {} 735 | }; 736 | 737 | /** 738 | * Observer is a base class for intrusive lifetime tracking of objects. 739 | * 740 | * This is an alternative to trackable pointers, such as std::shared_ptr, 741 | * and manual connection management by keeping connection objects in scope. 742 | * Deriving from this class allows automatic disconnection of all the slots 743 | * connected to any signal when an instance is destroyed. 744 | */ 745 | template 746 | struct observer_base : private detail::observer_type { 747 | virtual ~observer_base() = default; 748 | 749 | protected: 750 | /** 751 | * Disconnect all signals connected to this object. 752 | * 753 | * To avoid invocation of slots on a semi-destructed instance, which may happen 754 | * in multi-threaded contexts, derived classes should call this method in their 755 | * destructor. This will ensure proper disconnection prior to the destruction. 756 | */ 757 | void disconnect_all() { 758 | std::unique_lock _{m_mutex}; 759 | m_connections.clear(); 760 | } 761 | 762 | private: 763 | template 764 | friend class signal_base; 765 | 766 | void add_connection(connection conn) { 767 | std::unique_lock _{m_mutex}; 768 | m_connections.emplace_back(std::move(conn)); 769 | } 770 | 771 | Lockable m_mutex; 772 | std::vector m_connections; 773 | }; 774 | 775 | /** 776 | * Specialization of observer_base to be used in single threaded contexts. 777 | */ 778 | using observer_st = observer_base; 779 | 780 | /** 781 | * Specialization of observer_base to be used in multi-threaded contexts. 782 | */ 783 | using observer = observer_base; 784 | 785 | 786 | namespace detail { 787 | 788 | // interface for cleanable objects, used to cleanup disconnected slots 789 | struct cleanable { 790 | virtual ~cleanable() = default; 791 | virtual void clean(slot_state *) = 0; 792 | }; 793 | 794 | template 795 | class slot_base; 796 | 797 | template 798 | using slot_ptr = std::shared_ptr>; 799 | 800 | 801 | /* A base class for slot objects. This base type only depends on slot argument 802 | * types, it will be used as an element in an intrusive singly-linked list of 803 | * slots, hence the public next member. 804 | */ 805 | template 806 | class slot_base : public slot_state { 807 | public: 808 | using base_types = trait::typelist; 809 | 810 | explicit slot_base(cleanable &c, group_id gid) 811 | : slot_state(gid) 812 | , cleaner(c) 813 | {} 814 | ~slot_base() override = default; 815 | 816 | // method effectively responsible for calling the "slot" function with 817 | // supplied arguments whenever emission happens. 818 | virtual void call_slot(Args...) = 0; 819 | 820 | template 821 | void operator()(U && ...u) { 822 | if (slot_state::connected() && !slot_state::blocked()) { 823 | call_slot(std::forward(u)...); 824 | } 825 | } 826 | 827 | // check if we are storing callable c 828 | template 829 | bool has_callable(const C &c) const { 830 | auto p = get_callable(); 831 | return eq_function_ptr(c, p); 832 | } 833 | 834 | template 835 | std::enable_if_t::must_check_object, bool> 836 | has_full_callable(const C &c) const { 837 | return has_callable(c) && check_class_type>(); 838 | } 839 | 840 | template 841 | std::enable_if_t::must_check_object, bool> 842 | has_full_callable(const C &c) const { 843 | return has_callable(c); 844 | } 845 | 846 | // check if we are storing object o 847 | template 848 | bool has_object(const O &o) const { 849 | return get_object() == get_object_ptr(o); 850 | } 851 | 852 | protected: 853 | void do_disconnect() final { 854 | cleaner.clean(this); 855 | } 856 | 857 | // retieve a pointer to the object embedded in the slot 858 | virtual obj_ptr get_object() const noexcept { 859 | return nullptr; 860 | } 861 | 862 | // retieve a pointer to the callable embedded in the slot 863 | virtual func_ptr get_callable() const noexcept { 864 | return get_function_ptr(nullptr); 865 | } 866 | 867 | #ifdef SIGSLOT_RTTI_ENABLED 868 | // retieve a pointer to the callable embedded in the slot 869 | virtual const std::type_info& get_callable_type() const noexcept { 870 | return typeid(nullptr); 871 | } 872 | 873 | private: 874 | template 875 | bool check_class_type() const { 876 | return typeid(U) == get_callable_type(); 877 | } 878 | 879 | #else 880 | template 881 | bool check_class_type() const { 882 | return false; 883 | } 884 | #endif 885 | 886 | private: 887 | cleanable &cleaner; 888 | }; 889 | 890 | /* 891 | * A slot object holds state information, and a callable to to be called 892 | * whenever the function call operator of its slot_base base class is called. 893 | */ 894 | template 895 | class slot final : public slot_base { 896 | public: 897 | template 898 | constexpr slot(cleanable &c, F && f, Gid gid) 899 | : slot_base(c, gid) 900 | , func{std::forward(f)} {} 901 | 902 | protected: 903 | void call_slot(Args ...args) override { 904 | func(args...); 905 | } 906 | 907 | func_ptr get_callable() const noexcept override { 908 | return get_function_ptr(func); 909 | } 910 | 911 | #ifdef SIGSLOT_RTTI_ENABLED 912 | const std::type_info& get_callable_type() const noexcept override { 913 | return typeid(func); 914 | } 915 | #endif 916 | 917 | private: 918 | std::decay_t func; 919 | }; 920 | 921 | /* 922 | * Variation of slot that prepends a connection object to the callable 923 | */ 924 | template 925 | class slot_extended final : public slot_base { 926 | public: 927 | template 928 | constexpr slot_extended(cleanable &c, F && f, group_id gid) 929 | : slot_base(c, gid) 930 | , func{std::forward(f)} {} 931 | 932 | connection conn; 933 | 934 | protected: 935 | void call_slot(Args ...args) override { 936 | func(conn, args...); 937 | } 938 | 939 | func_ptr get_callable() const noexcept override { 940 | return get_function_ptr(func); 941 | } 942 | 943 | #ifdef SIGSLOT_RTTI_ENABLED 944 | const std::type_info& get_callable_type() const noexcept override { 945 | return typeid(func); 946 | } 947 | #endif 948 | 949 | private: 950 | std::decay_t func; 951 | }; 952 | 953 | /* 954 | * A slot object holds state information, an object and a pointer over member 955 | * function to be called whenever the function call operator of its slot_base 956 | * base class is called. 957 | */ 958 | template 959 | class slot_pmf final : public slot_base { 960 | public: 961 | template 962 | constexpr slot_pmf(cleanable &c, F && f, P && p, group_id gid) 963 | : slot_base(c, gid) 964 | , pmf{std::forward(f)} 965 | , ptr{std::forward

(p)} {} 966 | 967 | protected: 968 | void call_slot(Args ...args) override { 969 | ((*ptr).*pmf)(args...); 970 | } 971 | 972 | func_ptr get_callable() const noexcept override { 973 | return get_function_ptr(pmf); 974 | } 975 | 976 | obj_ptr get_object() const noexcept override { 977 | return get_object_ptr(ptr); 978 | } 979 | 980 | #ifdef SIGSLOT_RTTI_ENABLED 981 | const std::type_info& get_callable_type() const noexcept override { 982 | return typeid(pmf); 983 | } 984 | #endif 985 | 986 | private: 987 | std::decay_t pmf; 988 | std::decay_t ptr; 989 | }; 990 | 991 | /* 992 | * Variation of slot that prepends a connection object to the callable 993 | */ 994 | template 995 | class slot_pmf_extended final : public slot_base { 996 | public: 997 | template 998 | constexpr slot_pmf_extended(cleanable &c, F && f, P && p, group_id gid) 999 | : slot_base(c, gid) 1000 | , pmf{std::forward(f)} 1001 | , ptr{std::forward

(p)} {} 1002 | 1003 | connection conn; 1004 | 1005 | protected: 1006 | void call_slot(Args ...args) override { 1007 | ((*ptr).*pmf)(conn, args...); 1008 | } 1009 | 1010 | func_ptr get_callable() const noexcept override { 1011 | return get_function_ptr(pmf); 1012 | } 1013 | obj_ptr get_object() const noexcept override { 1014 | return get_object_ptr(ptr); 1015 | } 1016 | 1017 | #ifdef SIGSLOT_RTTI_ENABLED 1018 | const std::type_info& get_callable_type() const noexcept override { 1019 | return typeid(pmf); 1020 | } 1021 | #endif 1022 | 1023 | private: 1024 | std::decay_t pmf; 1025 | std::decay_t ptr; 1026 | }; 1027 | 1028 | /* 1029 | * An implementation of a slot that tracks the life of a supplied object 1030 | * through a weak pointer in order to automatically disconnect the slot 1031 | * on said object destruction. 1032 | */ 1033 | template 1034 | class slot_tracked final : public slot_base { 1035 | public: 1036 | template 1037 | constexpr slot_tracked(cleanable &c, F && f, P && p, group_id gid) 1038 | : slot_base(c, gid) 1039 | , func{std::forward(f)} 1040 | , ptr{std::forward

(p)} 1041 | {} 1042 | 1043 | bool connected() const noexcept override { 1044 | return !ptr.expired() && slot_state::connected(); 1045 | } 1046 | 1047 | protected: 1048 | void call_slot(Args ...args) override { 1049 | auto sp = ptr.lock(); 1050 | if (!sp) { 1051 | slot_state::disconnect(); 1052 | return; 1053 | } 1054 | if (slot_state::connected()) { 1055 | func(args...); 1056 | } 1057 | } 1058 | 1059 | func_ptr get_callable() const noexcept override { 1060 | return get_function_ptr(func); 1061 | } 1062 | 1063 | obj_ptr get_object() const noexcept override { 1064 | return get_object_ptr(ptr); 1065 | } 1066 | 1067 | #ifdef SIGSLOT_RTTI_ENABLED 1068 | const std::type_info& get_callable_type() const noexcept override { 1069 | return typeid(func); 1070 | } 1071 | #endif 1072 | 1073 | private: 1074 | std::decay_t func; 1075 | std::decay_t ptr; 1076 | }; 1077 | 1078 | /* 1079 | * An implementation of a slot as a pointer over member function, that tracks 1080 | * the life of a supplied object through a weak pointer in order to automatically 1081 | * disconnect the slot on said object destruction. 1082 | */ 1083 | template 1084 | class slot_pmf_tracked final : public slot_base { 1085 | public: 1086 | template 1087 | constexpr slot_pmf_tracked(cleanable &c, F && f, P && p, group_id gid) 1088 | : slot_base(c, gid) 1089 | , pmf{std::forward(f)} 1090 | , ptr{std::forward

(p)} 1091 | {} 1092 | 1093 | bool connected() const noexcept override { 1094 | return !ptr.expired() && slot_state::connected(); 1095 | } 1096 | 1097 | protected: 1098 | void call_slot(Args ...args) override { 1099 | auto sp = ptr.lock(); 1100 | if (!sp) { 1101 | slot_state::disconnect(); 1102 | return; 1103 | } 1104 | if (slot_state::connected()) { 1105 | ((*sp).*pmf)(args...); 1106 | } 1107 | } 1108 | 1109 | func_ptr get_callable() const noexcept override { 1110 | return get_function_ptr(pmf); 1111 | } 1112 | 1113 | obj_ptr get_object() const noexcept override { 1114 | return get_object_ptr(ptr); 1115 | } 1116 | 1117 | #ifdef SIGSLOT_RTTI_ENABLED 1118 | const std::type_info& get_callable_type() const noexcept override { 1119 | return typeid(pmf); 1120 | } 1121 | #endif 1122 | 1123 | private: 1124 | std::decay_t pmf; 1125 | std::decay_t ptr; 1126 | }; 1127 | 1128 | } // namespace detail 1129 | 1130 | 1131 | /** 1132 | * signal_base is an implementation of the observer pattern, through the use 1133 | * of an emitting object and slots that are connected to the signal and called 1134 | * with supplied arguments when a signal is emitted. 1135 | * 1136 | * signal_base is the general implementation, whose locking policy must be 1137 | * set in order to decide thread safety guarantees. signal and signal_st 1138 | * are partial specializations for multi-threaded and single-threaded use. 1139 | * 1140 | * It does not allow slots to return a value. 1141 | * 1142 | * Slot execution order can be constrained by assigning group ids to the slots. 1143 | * The execution order of slots in a same group is unspecified and should not be 1144 | * relied upon, however groups are executed in ascending group ids order. When 1145 | * the group id of a slot is not set, it is assigned to the group 0. Group ids 1146 | * can have any value in the range of signed 32 bit integers. 1147 | * 1148 | * @tparam Lockable a lock type to decide the lock policy 1149 | * @tparam T... the argument types of the emitting and slots functions. 1150 | */ 1151 | template 1152 | class signal_base final : public detail::cleanable { 1153 | template 1154 | using is_thread_safe = std::integral_constant::value>; 1155 | 1156 | template 1157 | using cow_type = std::conditional_t::value, 1158 | detail::copy_on_write, U>; 1159 | 1160 | template 1161 | using cow_copy_type = std::conditional_t::value, 1162 | detail::copy_on_write, const U&>; 1163 | 1164 | using lock_type = std::unique_lock; 1165 | using slot_base = detail::slot_base; 1166 | using slot_ptr = detail::slot_ptr; 1167 | using slots_type = std::vector; 1168 | struct group_type { slots_type slts; group_id gid; }; 1169 | using list_type = std::vector; // kept ordered by ascending gid 1170 | 1171 | public: 1172 | using arg_list = trait::typelist; 1173 | using ext_arg_list = trait::typelist; 1174 | 1175 | signal_base() noexcept : m_block(false) {} 1176 | ~signal_base() override { 1177 | disconnect_all(); 1178 | } 1179 | 1180 | signal_base(const signal_base&) = delete; 1181 | signal_base & operator=(const signal_base&) = delete; 1182 | 1183 | signal_base(signal_base && o) /* not noexcept */ 1184 | : m_block{o.m_block.load()} 1185 | { 1186 | lock_type lock(o.m_mutex); 1187 | using std::swap; 1188 | swap(m_slots, o.m_slots); 1189 | } 1190 | 1191 | signal_base & operator=(signal_base && o) /* not noexcept */ { 1192 | lock_type lock1(m_mutex, std::defer_lock); 1193 | lock_type lock2(o.m_mutex, std::defer_lock); 1194 | std::lock(lock1, lock2); 1195 | 1196 | using std::swap; 1197 | swap(m_slots, o.m_slots); 1198 | m_block.store(o.m_block.exchange(m_block.load())); 1199 | return *this; 1200 | } 1201 | 1202 | /** 1203 | * Emit a signal 1204 | * 1205 | * Effect: All non blocked and connected slot functions will be called 1206 | * with supplied arguments. 1207 | * Safety: With proper locking (see pal::signal), emission can happen from 1208 | * multiple threads simultaneously. The guarantees only apply to the 1209 | * signal object, it does not cover thread safety of potentially 1210 | * shared state used in slot functions. 1211 | * 1212 | * @param a... arguments to emit 1213 | */ 1214 | template 1215 | void operator()(U && ...a) const { 1216 | if (m_block) { 1217 | return; 1218 | } 1219 | 1220 | // Reference to the slots to execute them out of the lock 1221 | // a copy may occur if another thread writes to it. 1222 | cow_copy_type ref = slots_reference(); 1223 | 1224 | for (const auto &group : detail::cow_read(ref)) { 1225 | for (const auto &s : group.slts) { 1226 | s->operator()(a...); 1227 | } 1228 | } 1229 | } 1230 | 1231 | /** 1232 | * Connect a callable of compatible arguments 1233 | * 1234 | * Effect: Creates and stores a new slot responsible for executing the 1235 | * supplied callable for every subsequent signal emission. 1236 | * Safety: Thread-safety depends on locking policy. 1237 | * 1238 | * @param c a callable 1239 | * @param gid an identifier that can be used to order slot execution 1240 | * @return a connection object that can be used to interact with the slot 1241 | */ 1242 | template 1243 | std::enable_if_t, connection> 1244 | connect(Callable && c, group_id gid = 0) { 1245 | using slot_t = detail::slot; 1246 | auto s = make_slot(std::forward(c), gid); 1247 | connection conn(s); 1248 | add_slot(std::move(s)); 1249 | return conn; 1250 | } 1251 | 1252 | /** 1253 | * Connect a callable with an additional connection argument 1254 | * 1255 | * The callable's first argument must be of type connection. This overload 1256 | * the callable to manage it's own connection through this argument. 1257 | * 1258 | * @param c a callable 1259 | * @param gid an identifier that can be used to order slot execution 1260 | * @return a connection object that can be used to interact with the slot 1261 | */ 1262 | template 1263 | std::enable_if_t, connection> 1264 | connect_extended(Callable && c, group_id gid = 0) { 1265 | using slot_t = detail::slot_extended; 1266 | auto s = make_slot(std::forward(c), gid); 1267 | connection conn(s); 1268 | std::static_pointer_cast(s)->conn = conn; 1269 | add_slot(std::move(s)); 1270 | return conn; 1271 | } 1272 | 1273 | /** 1274 | * Overload of connect for pointers over member functions derived from 1275 | * observer 1276 | * 1277 | * @param pmf a pointer over member function 1278 | * @param ptr an object pointer derived from observer 1279 | * @param gid an identifier that can be used to order slot execution 1280 | * @return a connection object that can be used to interact with the slot 1281 | */ 1282 | template 1283 | std::enable_if_t && 1284 | trait::is_observer_v, connection> 1285 | connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) { 1286 | using slot_t = detail::slot_pmf; 1287 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1288 | connection conn(s); 1289 | add_slot(std::move(s)); 1290 | ptr->add_connection(conn); 1291 | return conn; 1292 | } 1293 | 1294 | /** 1295 | * Overload of connect for pointers over member functions 1296 | * 1297 | * @param pmf a pointer over member function 1298 | * @param ptr an object pointer 1299 | * @param gid an identifier that can be used to order slot execution 1300 | * @return a connection object that can be used to interact with the slot 1301 | */ 1302 | template 1303 | std::enable_if_t && 1304 | !trait::is_observer_v && 1305 | !trait::is_weak_ptr_compatible_v, connection> 1306 | connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) { 1307 | using slot_t = detail::slot_pmf; 1308 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1309 | connection conn(s); 1310 | add_slot(std::move(s)); 1311 | return conn; 1312 | } 1313 | 1314 | /** 1315 | * Overload of connect for pointer over member functions and 1316 | * 1317 | * @param pmf a pointer over member function 1318 | * @param ptr an object pointer 1319 | * @param gid an identifier that can be used to order slot execution 1320 | * @return a connection object that can be used to interact with the slot 1321 | */ 1322 | template 1323 | std::enable_if_t && 1324 | !trait::is_weak_ptr_compatible_v, connection> 1325 | connect_extended(Pmf && pmf, Ptr && ptr, group_id gid = 0) { 1326 | using slot_t = detail::slot_pmf_extended; 1327 | auto s = make_slot(std::forward(pmf), std::forward(ptr), gid); 1328 | connection conn(s); 1329 | std::static_pointer_cast(s)->conn = conn; 1330 | add_slot(std::move(s)); 1331 | return conn; 1332 | } 1333 | 1334 | /** 1335 | * Overload of connect for lifetime object tracking and automatic disconnection 1336 | * 1337 | * Ptr must be convertible to an object following a loose form of weak pointer 1338 | * concept, by implementing the ADL-detected conversion function to_weak(). 1339 | * 1340 | * This overload covers the case of a pointer over member function and a 1341 | * trackable pointer of that class. 1342 | * 1343 | * Note: only weak references are stored, a slot does not extend the lifetime 1344 | * of a suppied object. 1345 | * 1346 | * @param pmf a pointer over member function 1347 | * @param ptr a trackable object pointer 1348 | * @param gid an identifier that can be used to order slot execution 1349 | * @return a connection object that can be used to interact with the slot 1350 | */ 1351 | template 1352 | std::enable_if_t && 1353 | trait::is_weak_ptr_compatible_v, connection> 1354 | connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) { 1355 | using trait::to_weak; 1356 | auto w = to_weak(std::forward(ptr)); 1357 | using slot_t = detail::slot_pmf_tracked; 1358 | auto s = make_slot(std::forward(pmf), w, gid); 1359 | connection conn(s); 1360 | add_slot(std::move(s)); 1361 | return conn; 1362 | } 1363 | 1364 | /** 1365 | * Overload of connect for lifetime object tracking and automatic disconnection 1366 | * 1367 | * Trackable must be convertible to an object following a loose form of weak 1368 | * pointer concept, by implementing the ADL-detected conversion function to_weak(). 1369 | * 1370 | * This overload covers the case of a standalone callable and unrelated trackable 1371 | * object. 1372 | * 1373 | * Note: only weak references are stored, a slot does not extend the lifetime 1374 | * of a suppied object. 1375 | * 1376 | * @param c a callable 1377 | * @param ptr a trackable object pointer 1378 | * @param gid an identifier that can be used to order slot execution 1379 | * @return a connection object that can be used to interact with the slot 1380 | */ 1381 | template 1382 | std::enable_if_t && 1383 | trait::is_weak_ptr_compatible_v, connection> 1384 | connect(Callable && c, Trackable && ptr, group_id gid = 0) { 1385 | using trait::to_weak; 1386 | auto w = to_weak(std::forward(ptr)); 1387 | using slot_t = detail::slot_tracked; 1388 | auto s = make_slot(std::forward(c), w, gid); 1389 | connection conn(s); 1390 | add_slot(std::move(s)); 1391 | return conn; 1392 | } 1393 | 1394 | /** 1395 | * Creates a connection whose duration is tied to the return object 1396 | * Use the same semantics as connect 1397 | */ 1398 | template 1399 | scoped_connection connect_scoped(CallArgs && ...args) { 1400 | return connect(std::forward(args)...); 1401 | } 1402 | 1403 | /** 1404 | * Disconnect slots bound to a callable 1405 | * 1406 | * Effect: Disconnects all the slots bound to the callable in argument. 1407 | * Safety: Thread-safety depends on locking policy. 1408 | * 1409 | * If the callable is a free or static member function, this overload is always 1410 | * available. However, RTTI is needed for it to work for pointer to member 1411 | * functions, function objects or and (references to) lambdas, because the 1412 | * C++ spec does not mandate the pointers to member functions to be unique. 1413 | * 1414 | * @param c a callable 1415 | * @return the number of disconnected slots 1416 | */ 1417 | template 1418 | std::enable_if_t<(trait::is_callable_v || 1419 | trait::is_callable_v || 1420 | trait::is_pmf_v) && 1421 | detail::function_traits::is_disconnectable, size_t> 1422 | disconnect(const Callable &c) { 1423 | return disconnect_if([&] (const auto &s) { 1424 | return s->has_full_callable(c); 1425 | }); 1426 | } 1427 | 1428 | /** 1429 | * Disconnect slots bound to this object 1430 | * 1431 | * Effect: Disconnects all the slots bound to the object or tracked object 1432 | * in argument. 1433 | * Safety: Thread-safety depends on locking policy. 1434 | * 1435 | * The object may be a pointer or trackable object. 1436 | * 1437 | * @param obj an object 1438 | * @return the number of disconnected slots 1439 | */ 1440 | template 1441 | std::enable_if_t && 1442 | !trait::is_callable_v && 1443 | !trait::is_pmf_v, size_t> 1444 | disconnect(const Obj &obj) { 1445 | return disconnect_if([&] (const auto &s) { 1446 | return s->has_object(obj); 1447 | }); 1448 | } 1449 | 1450 | /** 1451 | * Disconnect slots bound both to a callable and object 1452 | * 1453 | * Effect: Disconnects all the slots bound to the callable and object in argument. 1454 | * Safety: Thread-safety depends on locking policy. 1455 | * 1456 | * For naked pointers, the Callable is expected to be a pointer over member 1457 | * function. If obj is trackable, any kind of Callable can be used. 1458 | * 1459 | * @param c a callable 1460 | * @param obj an object 1461 | * @return the number of disconnected slots 1462 | */ 1463 | template 1464 | size_t disconnect(const Callable &c, const Obj &obj) { 1465 | return disconnect_if([&] (const auto &s) { 1466 | return s->has_object(obj) && s->has_callable(c); 1467 | }); 1468 | } 1469 | 1470 | /** 1471 | * Disconnect slots in a particular group 1472 | * 1473 | * Effect: Disconnects all the slots in the group id in argument. 1474 | * Safety: Thread-safety depends on locking policy. 1475 | * 1476 | * @param gid a group id 1477 | * @return the number of disconnected slots 1478 | */ 1479 | size_t disconnect(group_id gid) { 1480 | lock_type lock(m_mutex); 1481 | for (auto &group : detail::cow_write(m_slots)) { 1482 | if (group.gid == gid) { 1483 | size_t count = group.slts.size(); 1484 | group.slts.clear(); 1485 | return count; 1486 | } 1487 | } 1488 | return 0; 1489 | } 1490 | 1491 | /** 1492 | * Disconnects all the slots 1493 | * Safety: Thread safety depends on locking policy 1494 | */ 1495 | void disconnect_all() { 1496 | lock_type lock(m_mutex); 1497 | clear(); 1498 | } 1499 | 1500 | /** 1501 | * Blocks signal emission 1502 | * Safety: thread safe 1503 | */ 1504 | void block() noexcept { 1505 | m_block.store(true); 1506 | } 1507 | 1508 | /** 1509 | * Unblocks signal emission 1510 | * Safety: thread safe 1511 | */ 1512 | void unblock() noexcept { 1513 | m_block.store(false); 1514 | } 1515 | 1516 | /** 1517 | * Tests blocking state of signal emission 1518 | */ 1519 | bool blocked() const noexcept { 1520 | return m_block.load(); 1521 | } 1522 | 1523 | /** 1524 | * Get number of connected slots 1525 | * Safety: thread safe 1526 | */ 1527 | size_t slot_count() noexcept { 1528 | cow_copy_type ref = slots_reference(); 1529 | size_t count = 0; 1530 | for (const auto &g : detail::cow_read(ref)) { 1531 | count += g.slts.size(); 1532 | } 1533 | return count; 1534 | } 1535 | 1536 | protected: 1537 | /** 1538 | * remove disconnected slots 1539 | */ 1540 | void clean(detail::slot_state *state) override { 1541 | lock_type lock(m_mutex); 1542 | const auto idx = state->index(); 1543 | const auto gid = state->group(); 1544 | 1545 | // find the group 1546 | for (auto &group : detail::cow_write(m_slots)) { 1547 | if (group.gid == gid) { 1548 | auto &slts = group.slts; 1549 | 1550 | // ensure we have the right slot, in case of concurrent cleaning 1551 | if (idx < slts.size() && slts[idx] && slts[idx].get() == state) { 1552 | std::swap(slts[idx], slts.back()); 1553 | slts[idx]->index() = idx; 1554 | slts.pop_back(); 1555 | } 1556 | 1557 | return; 1558 | } 1559 | } 1560 | } 1561 | 1562 | private: 1563 | // used to get a reference to the slots for reading 1564 | inline cow_copy_type slots_reference() const { 1565 | lock_type lock(m_mutex); 1566 | return m_slots; 1567 | } 1568 | 1569 | // create a new slot 1570 | template 1571 | inline auto make_slot(A && ...a) { 1572 | return detail::make_shared(*this, std::forward(a)...); 1573 | } 1574 | 1575 | // add the slot to the list of slots of the right group 1576 | void add_slot(slot_ptr &&s) { 1577 | const group_id gid = s->group(); 1578 | 1579 | lock_type lock(m_mutex); 1580 | auto &groups = detail::cow_write(m_slots); 1581 | 1582 | // find the group 1583 | auto it = groups.begin(); 1584 | while (it != groups.end() && it->gid < gid) { 1585 | it++; 1586 | } 1587 | 1588 | // create a new group if necessary 1589 | if (it == groups.end() || it->gid != gid) { 1590 | it = groups.insert(it, {{}, gid}); 1591 | } 1592 | 1593 | // add the slot 1594 | s->index() = it->slts.size(); 1595 | it->slts.push_back(std::move(s)); 1596 | } 1597 | 1598 | // disconnect a slot if a condition occurs 1599 | template 1600 | size_t disconnect_if(Cond && cond) { 1601 | lock_type lock(m_mutex); 1602 | auto &groups = detail::cow_write(m_slots); 1603 | 1604 | size_t count = 0; 1605 | 1606 | for (auto &group : groups) { 1607 | auto &slts = group.slts; 1608 | size_t i = 0; 1609 | while (i < slts.size()) { 1610 | if (cond(slts[i])) { 1611 | std::swap(slts[i], slts.back()); 1612 | slts[i]->index() = i; 1613 | slts.pop_back(); 1614 | ++count; 1615 | } else { 1616 | ++i; 1617 | } 1618 | } 1619 | } 1620 | 1621 | return count; 1622 | } 1623 | 1624 | // to be called under lock: remove all the slots 1625 | void clear() { 1626 | detail::cow_write(m_slots).clear(); 1627 | } 1628 | 1629 | private: 1630 | mutable Lockable m_mutex; 1631 | cow_type m_slots; 1632 | std::atomic m_block; 1633 | }; 1634 | 1635 | 1636 | /** 1637 | * Freestanding connect function that defers to the `signal_base::connect` member. 1638 | */ 1639 | template 1640 | std::enable_if_t>, connection> 1641 | connect(signal_base &sig, Arg &&arg, Args && ...args) 1642 | { 1643 | return sig.connect(std::forward(arg), std::forward(args)...); 1644 | } 1645 | 1646 | /** 1647 | * Freestanding connect function that chains one signal to another. 1648 | */ 1649 | template 1650 | connection connect(signal_base &sig1, 1651 | signal_base &sig2, 1652 | Args && ...args) 1653 | { 1654 | return sig1.connect(detail::signal_wrapper>{std::addressof(sig2)}, 1655 | std::forward(args)...); 1656 | } 1657 | 1658 | 1659 | /** 1660 | * Specialization of signal_base to be used in single threaded contexts. 1661 | * Slot connection, disconnection and signal emission are not thread-safe. 1662 | * The performance improvement over the thread-safe variant is not impressive, 1663 | * so this is not very useful. 1664 | */ 1665 | template 1666 | using signal_st = signal_base; 1667 | 1668 | /** 1669 | * Specialization of signal_base to be used in multi-threaded contexts. 1670 | * Slot connection, disconnection and signal emission are thread-safe. 1671 | * 1672 | * Recursive signal emission and emission cycles are supported too. 1673 | */ 1674 | template 1675 | using signal = signal_base; 1676 | 1677 | } // namespace sigslot 1678 | 1679 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Sigslot, a signal-slot library 2 | 3 | Sigslot is a header-only, thread safe implementation of signal-slots for C++. 4 | 5 | ## Features 6 | 7 | The main goal was to replace Boost.Signals2. 8 | 9 | Apart from the usual features, it offers 10 | 11 | - Thread safety, 12 | - Object lifetime tracking for automatic slot disconnection (extensible through ADL), 13 | - RAII connection management, 14 | - Slot groups to enforce slots execution order, 15 | - Reasonable performance. and a simple and straightforward implementation. 16 | 17 | Sigslot is unit-tested and should be reliable and stable enough to replace Boost Signals2. 18 | 19 | The tests run cleanly under the address, thread and undefined behaviour sanitizers. 20 | 21 | Many implementations allow signal return types, Sigslot does not because I have 22 | no use for them. If I can be convinced of otherwise I may change my mind later on. 23 | 24 | ## Installation 25 | 26 | No compilation or installation is required, just include `sigslot/signal.hpp` 27 | and use it. Sigslot currently depends on a C++14 compliant compiler, but if need 28 | arises it may be retrofitted to C++11. It is known to work with Clang 4.0 and GCC 29 | 5.0+ compilers on GNU Linux, MSVC 2017 and up, Clang-cl and MinGW on Windows. 30 | 31 | However, be aware of a potential gotcha on Windows with MSVC and Clang-Cl compilers, 32 | which may need the `/OPT:NOICF` linker flags in exceptional situations. Read The 33 | Implementation Details chapter for an explanation. 34 | 35 | A CMake list file is supplied for installation purpose and generating a CMake import 36 | module. This is the preferred installation method. The `Pal::Sigslot` imported target 37 | is available and already applies the needed linker flags. It is also required for 38 | examples and tests, which optionally depend on Qt5 and Boost for adapters unit tests. 39 | 40 | ```cmake 41 | # Using Sigslot from cmake 42 | find_package(PalSigslot) 43 | 44 | add_executable(MyExe main.cpp) 45 | target_link_libraries(MyExe PRIVATE Pal::Sigslot) 46 | ``` 47 | 48 | A configuration option `SIGSLOT_REDUCE_COMPILE_TIME` is available at configuration 49 | time. When activated, it attempts to reduce code bloat by avoiding heavy template 50 | instantiations resulting from calls to `std::make_shared`. 51 | This option is off by default, but can be activated for those who wish to favor 52 | code size and compilation time at the expanse of slightly less efficient code. 53 | 54 | Installation may be done using the following instructions from the root directory: 55 | 56 | ```sh 57 | mkdir build && cd build 58 | cmake .. -DSIGSLOT_REDUCE_COMPILE_TIME=ON -DCMAKE_INSTALL_PREFIX=~/local 59 | cmake --build . --target install 60 | 61 | # If you want to compile examples: 62 | cmake --build . --target sigslot-examples 63 | 64 | # And compile/execute unit tests: 65 | cmake --build . --target sigslot-tests 66 | ``` 67 | 68 | ### CMake FetchContent 69 | 70 | `Pal::Sigslot` can also be integrated using the [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) method. 71 | 72 | ```cmake 73 | include(FetchContent) 74 | 75 | FetchContent_Declare( 76 | sigslot 77 | GIT_REPOSITORY https://github.com/palacaze/sigslot 78 | GIT_TAG 19a6f0f5ea11fc121fe67f81fd5e491f2d7a4637 # v1.2.0 79 | ) 80 | FetchContent_MakeAvailable(sigslot) 81 | 82 | add_executable(MyExe main.cpp) 83 | target_link_libraries(MyExe PRIVATE Pal::Sigslot) 84 | ``` 85 | 86 | ## Documentation 87 | 88 | Sigslot implements the signal-slot construct popular in UI frameworks, making it 89 | easy to use the observer pattern or event-based programming. The main entry point 90 | of the library is the `sigslot::signal` class template. 91 | 92 | A signal is an object that can emit typed notifications, really values parametrized 93 | after the signal class template parameters, and register any number of notification 94 | handlers (callables) of compatible argument types to be executed with the values 95 | supplied whenever a signal emission happens. In signal-slot parlance this is called 96 | connecting a slot to a signal, where a "slot" represents a callable instance and 97 | a "connection" can be thought of as a conceptual link from signal to slot. 98 | 99 | All the snippets presented below are available in compilable source code form in 100 | the example subdirectory. 101 | 102 | ### Basic usage 103 | 104 | Here is a first example that showcases the most basic features of the library. 105 | 106 | We first declare a parameter-free signal `sig`, then we proceed to connect several 107 | slots and at last emit a signal which triggers the invocation of every slot callable 108 | connected beforehand. Notice how The library handles diverse forms of callables. 109 | 110 | ```cpp 111 | #include 112 | #include 113 | 114 | void f() { std::cout << "free function\n"; } 115 | 116 | struct s { 117 | void m() { std::cout << "member function\n"; } 118 | static void sm() { std::cout << "static member function\n"; } 119 | }; 120 | 121 | struct o { 122 | void operator()() { std::cout << "function object\n"; } 123 | }; 124 | 125 | int main() { 126 | s d; 127 | auto lambda = []() { std::cout << "lambda\n"; }; 128 | auto gen_lambda = [](auto && ...a) { std::cout << "generic lambda\n"; }; 129 | 130 | // declare a signal instance with no arguments 131 | sigslot::signal<> sig; 132 | 133 | // connect slots 134 | sig.connect(f); 135 | sig.connect(&s::m, &d); 136 | sig.connect(&s::sm); 137 | sig.connect(o()); 138 | sig.connect(lambda); 139 | sig.connect(gen_lambda); 140 | 141 | // a free connect() function is also available 142 | sigslot::connect(sig, f); 143 | 144 | // emit a signal 145 | sig(); 146 | } 147 | ``` 148 | 149 | By default, the slot invocation order when emitting a signal is unspecified, please 150 | do not rely on it being always the same. You may constrain a particular invocation 151 | order by using slot groups, which are presented later on. 152 | 153 | ### Signal with arguments 154 | 155 | That first example was simple but not so useful, let us move on to a signal that 156 | emits values instead. A signal can emit any number of arguments, below. 157 | 158 | ```cpp 159 | #include 160 | #include 161 | #include 162 | 163 | struct foo { 164 | // Notice how we accept a double as first argument here. 165 | // This is fine because float is convertible to double. 166 | // 's' is a reference and can thus be modified. 167 | void bar(double d, int i, bool b, std::string &s) { 168 | s = b ? std::to_string(i) : std::to_string(d); 169 | } 170 | }; 171 | 172 | // Function objects can cope with default arguments and overloading. 173 | // It does not work with static and member functions. 174 | struct obj { 175 | void operator()(float, int, bool, std::string &, int = 0) { 176 | std::cout << "I was here\n"; 177 | } 178 | 179 | void operator()() {} 180 | }; 181 | 182 | int main() { 183 | // declare a signal with float, int, bool and string& arguments 184 | sigslot::signal sig; 185 | 186 | // a generic lambda that prints its arguments to stdout 187 | auto printer = [] (auto a, auto && ...args) { 188 | std::cout << a; 189 | (void)std::initializer_list{ 190 | ((void)(std::cout << " " << args), 1)... 191 | }; 192 | std::cout << "\n"; 193 | }; 194 | 195 | // connect the slots 196 | foo ff; 197 | sig.connect(printer); 198 | sig.connect(&foo::bar, &ff); 199 | sig.connect(obj()); 200 | 201 | float f = 1.f; 202 | short i = 2; // convertible to int 203 | std::string s = "0"; 204 | 205 | // emit a signal 206 | sig(f, i, false, s); 207 | sig(f, i, true, s); 208 | } 209 | ``` 210 | 211 | As shown, slots arguments types don't need to be strictly identical to the signal 212 | template parameters, being convertible-from is fine. Generic arguments are fine too, 213 | as shown with the `printer` generic lambda (which could have been written as a 214 | function template too). 215 | 216 | Right now there are two limitations that I can think of with respect to callable 217 | handling: default arguments and function overloading. Both are working correctly 218 | in the case of function objects but will fail to compile with static and member 219 | functions, for different but related reasons. 220 | 221 | #### Coping with overloaded functions 222 | 223 | Consider the following piece of code: 224 | 225 | ```cpp 226 | struct foo { 227 | void bar(double d); 228 | void bar(); 229 | }; 230 | ``` 231 | 232 | What should `&foo::bar` refer to? As per overloading, this pointer over member 233 | function does not map to a unique symbol, so the compiler won't be able to pick 234 | the right symbol. One way of resolving the right symbol is to explicitly cast the 235 | function pointer to the right function type. Here is an example that does just that 236 | using a little helper tool for a lighter syntax (In fact I will probably add this 237 | to the library soon). 238 | 239 | ```cpp 240 | #include 241 | 242 | template 243 | constexpr auto overload(void (C::*ptr)(Args...)) { 244 | return ptr; 245 | } 246 | 247 | template 248 | constexpr auto overload(void (*ptr)(Args...)) { 249 | return ptr; 250 | } 251 | 252 | struct obj { 253 | void operator()(int) const {} 254 | void operator()() {} 255 | }; 256 | 257 | struct foo { 258 | void bar(int) {} 259 | void bar() {} 260 | 261 | static void baz(int) {} 262 | static void baz() {} 263 | }; 264 | 265 | void moo(int) {} 266 | void moo() {} 267 | 268 | int main() { 269 | sigslot::signal sig; 270 | 271 | // connect the slots, casting to the right overload if necessary 272 | foo ff; 273 | sig.connect(overload(&foo::bar), &ff); 274 | sig.connect(overload(&foo::baz)); 275 | sig.connect(overload(&moo)); 276 | sig.connect(obj()); 277 | 278 | sig(0); 279 | 280 | return 0; 281 | } 282 | ``` 283 | 284 | #### Coping with function with default arguments 285 | 286 | Default arguments are not part of the function type signature, and can be redefined, 287 | so they are really difficult to deal with. When connecting a slot to a signal, the 288 | library determines if the supplied callable can be invoked with the signal argument 289 | types, but at this point the existence of default function arguments is unknown 290 | so there might be a mismatch in the number of arguments. 291 | 292 | A simple work around for this use case would is to create a bind adapter, in fact 293 | we can even make it quite generic like so: 294 | 295 | ```cpp 296 | #include 297 | 298 | #define ADAPT(func) \ 299 | [=](auto && ...a) { (func)(std::forward(a)...); } 300 | 301 | void foo(int &i, int b = 1) { 302 | i += b; 303 | } 304 | 305 | int main() { 306 | int i = 0; 307 | 308 | // fine, all the arguments are handled 309 | sigslot::signal sig1; 310 | sig1.connect(foo); 311 | sig1(i, 2); 312 | 313 | // must wrap in an adapter 314 | i = 0; 315 | sigslot::signal sig2; 316 | sig2.connect(ADAPT(foo)); 317 | sig2(i); 318 | 319 | return 0; 320 | } 321 | ``` 322 | 323 | ### Connection management 324 | 325 | #### Connection object 326 | 327 | What was not made apparent until now is that `signal::connect()` actually returns 328 | a `sigslot::connection` object that may be used to manage the behaviour and lifetime 329 | of a signal-slot connection. `sigslot::connection` is a lightweight object (basically 330 | a `std::weak_ptr`) that allows interaction with an ongoing signal-slot connection 331 | and exposes the following features: 332 | 333 | - Status querying, that is testing whether a connection is valid, ongoing or facing destruction, 334 | - Connection (un)blocking, which allows to temporarily disable the invocation of a slot when a signal is emitted, 335 | - Disconnection of a slot, the destruction of a connection previously created via `signal::connect()`. 336 | 337 | A `sigslot::connection` does not tie a connection to a scope: this is not a RAII 338 | object, which explains why it can be copied. It can be however implicitly converted 339 | into a `sigslot::scoped_connection` which destroys the connection when going out 340 | of scope. 341 | 342 | Here is an example illustrating some of those features: 343 | 344 | ```cpp 345 | #include 346 | #include 347 | 348 | int i = 0; 349 | 350 | void f() { i += 1; } 351 | 352 | int main() { 353 | sigslot::signal<> sig; 354 | 355 | // keep a sigslot::connection object 356 | auto c1 = sig.connect(f); 357 | 358 | // disconnection 359 | sig(); // i == 1 360 | c1.disconnect(); 361 | sig(); // i == 1 362 | 363 | // scope based disconnection 364 | { 365 | sigslot::scoped_connection sc = sig.connect(f); 366 | sig(); // i == 2 367 | } 368 | 369 | sig(); // i == 2; 370 | 371 | 372 | // connection blocking 373 | auto c2 = sig.connect(f); 374 | sig(); // i == 3 375 | c2.block(); 376 | sig(); // i == 3 377 | c2.unblock(); 378 | sig(); // i == 4 379 | } 380 | ``` 381 | 382 | #### Extended connection signature 383 | 384 | Sigslot supports an extended slot signature with an additional `sigslot::connection` 385 | reference as first argument, which permits connection management from inside the 386 | slot. This extended signature is accessible using the `connect_extended()` method. 387 | 388 | ```cpp 389 | #include 390 | 391 | int main() { 392 | int i = 0; 393 | sigslot::signal<> sig; 394 | 395 | // extended connection 396 | auto f = [](auto &con) { 397 | i += 1; // do work 398 | con.disconnect(); // then disconnects 399 | }; 400 | 401 | sig.connect_extended(f); 402 | sig(); // i == 1 403 | sig(); // i == 1 because f was disconnected 404 | } 405 | ``` 406 | 407 | #### Automatic slot lifetime tracking 408 | 409 | The user must make sure that the lifetime of a slot exceeds the one of a signal, 410 | which may get tedious in complex software. To simplify this task, Sigslot can 411 | automatically disconnect slot object whose lifetime it is able to track. In order 412 | to do that, the slot must be convertible to a weak pointer of some form. 413 | 414 | `std::shared_ptr` and `std::weak_ptr` are supported out of the box, and adapters 415 | are provided to support `boost::shared_ptr`, `boost::weak_ptr` and Qt `QSharedPointer`, 416 | `QWeakPointer` and any class deriving from `QObject`. 417 | 418 | Other trackable objects can be added by declaring a `to_weak()` adapter function. 419 | 420 | ```cpp 421 | #include 422 | #include 423 | 424 | int sum = 0; 425 | 426 | struct s { 427 | void f(int i) { sum += i; } 428 | }; 429 | 430 | class MyObject : public QObject { 431 | Q_OBJECT 432 | public: 433 | void add(int i) const { sum += i; } 434 | }; 435 | 436 | int main() { 437 | sum = 0; 438 | signal sig; 439 | 440 | // track lifetime of object and also connect to a member function 441 | auto p = std::make_shared(); 442 | sig.connect(&s::f, p); 443 | 444 | sig(1); // sum == 1 445 | p.reset(); 446 | sig(1); // sum == 1 447 | 448 | // track an unrelated object lifetime 449 | struct dummy; 450 | auto l = [&](int i) { sum += i; }; 451 | 452 | auto d = std::make_shared(); 453 | sig.connect(l, d); 454 | sig(1); // sum == 2 455 | d.reset(); 456 | sig(1); // sum == 2 457 | 458 | // track a QObject 459 | { 460 | MyObject o; 461 | sig.connect(&MyObject::add, &o); 462 | 463 | sig(1); // sum == 3 464 | } 465 | 466 | sig(1); // sum == 3 467 | } 468 | ``` 469 | 470 | #### Intrusive slot lifetime tracking 471 | 472 | Another way of ensuring automatic disconnection of pointer over member functions 473 | slots is by explicitly inheriting from `sigslot::observer` or `sigslot::observer_st`. 474 | The former is thread-safe, contrary to the later. 475 | 476 | Here is an example usage. 477 | 478 | ```cpp 479 | #include 480 | 481 | int sum = 0; 482 | 483 | struct s : sigslot::observer_st { 484 | void f(int i) { sum += i; } 485 | }; 486 | 487 | struct s_mt : sigslot::observer { 488 | ~s_mt() { 489 | // Needed to ensure proper disconnection prior to object destruction 490 | // in multithreaded contexts. 491 | this->disconnect_all(); 492 | } 493 | 494 | void f(int i) { sum += i; } 495 | }; 496 | 497 | int main() { 498 | sum = 0; 499 | signal sig; 500 | 501 | { 502 | // Lifetime of object instance p is tracked 503 | s p; 504 | s_mt pm; 505 | sig.connect(&s::f, &p); 506 | sig.connect(&s_mt::f, &pm); 507 | sig(1); // sum == 2 508 | } 509 | 510 | // The slots got disconnected at instance destruction 511 | sig(1); // sum == 2 512 | } 513 | ``` 514 | 515 | The objects that use this intrusive approach may be connected to any number of 516 | unrelated signals. 517 | 518 | ### Disconnection without a connection object 519 | 520 | Support for slot disconnection by supplying an appropriate function signature, 521 | object pointer or tracker has been introduced in version 1.2.0. 522 | 523 | One can disconnect any number of slots using the `signal::disconnect()` method, 524 | which proposes 4 overloads to specify the disconnection criterion: 525 | 526 | - The first takes a reference to a callable. Any kind of callable can be passed, 527 | even pointers to member functions, function objects and lambdas, 528 | - The second takes a pointer to an object, for slots bound to a pointer to member 529 | function, or a tracking object, 530 | - The third overload takes both kinds of arguments at the same time and can be 531 | used to pinpoint a specific pair of object + callable. 532 | - The last overload takes a group id and disconnects all the slots in this group. 533 | 534 | Disconnection of lambdas is only possible for lambdas bound to a variable, due 535 | to their uniqueness. 536 | 537 | The second overload currently needs RTTI to disconnect from pointers to member 538 | functions, function objects and lambdas. This limitation does not apply to free 539 | and static member functions. The reasons stems from the fact that in C++, pointers 540 | to member functions of unrelated types are not comparable, contrary to pointers to 541 | free and static member functions. For instance, the pointer to member functions of 542 | virtual methods of different classes can have the same address (they kind of store 543 | the offset of the method into the vtable). 544 | 545 | However, Sigslot can be compiled with RTTI disabled and the overload will be 546 | deactivated for problematic cases. 547 | 548 | As a side node, this feature admittedly added more code than anticipated at first 549 | because it is a tricky and easy to get wrong. It has been designed carefully, with 550 | correctness in mind, and does not have any hidden costs unless you actually use it. 551 | 552 | Here is an example demonstrating the feature. 553 | 554 | ```cpp 555 | #include 556 | #include 557 | 558 | static int i = 0; 559 | 560 | void f1() { i += 1; } 561 | void f2() { i += 1; } 562 | 563 | struct s { 564 | void m1() { i += 1; } 565 | void m2() { i += 1; } 566 | void m3() { i += 1; } 567 | }; 568 | 569 | struct o { 570 | void operator()() { i += 1; } 571 | }; 572 | 573 | int main() { 574 | sigslot::signal<> sig; 575 | s s1; 576 | auto s2 = std::make_shared(); 577 | 578 | auto lbd = [&] { i += 1; }; 579 | 580 | sig.connect(f1); // #1 581 | sig.connect(f2); // #2 582 | sig.connect(&s::m1, &s1); // #3 583 | sig.connect(&s::m2, &s1); // #4 584 | sig.connect(&s::m3, &s1); // #5 585 | sig.connect(&s::m1, s2); // #6 586 | sig.connect(&s::m2, s2); // #7 587 | sig.connect(o{}); // #8 588 | sig.connect(lbd); // #9 589 | 590 | sig(); // i == 9 591 | 592 | sig.disconnect(f2); // #2 is removed 593 | sig.disconnect(&s::m1); // #3 and #6 are removed 594 | sig.disconnect(o{}); // #8 and is removed 595 | // sig.disconnect(&o::operator()); // same as the above, more efficient 596 | sig.disconnect(lbd); // #9 and is removed 597 | sig.disconnect(s2); // #7 is removed 598 | sig.disconnect(&s::m3, &s1); // #5 is removed, not #4 599 | 600 | sig(); // i == 11 601 | 602 | sig.disconnect_all(); // remove all remaining slots 603 | return 0; 604 | } 605 | ``` 606 | 607 | ### Enforcing slot invocation order with slot groups 608 | 609 | From version 1.2.0, slots can be assigned a group id in order to control the 610 | relative order of invocation of slots. 611 | 612 | The order of invocation of slots in a same group is unspecified and should not be 613 | relied upon, however slot groups are invoked in ascending group id order. 614 | When the group id of a slot is not set, it is assigned to the group 0. 615 | Group ids can have any value in the range of signed 32 bit integers. 616 | 617 | ```cpp 618 | #include 619 | #include 620 | #include 621 | 622 | int main() { 623 | sigslot::signal<> sig; 624 | 625 | // simply assigning a group id as last argument to connect 626 | sig.connect([] { std::puts("Second"); }, 1); 627 | sig.connect([] { std::puts("Last"); }, std::numeric_limits::max()); 628 | sig.connect([] { std::puts("First"); }, -10); 629 | sig(); 630 | 631 | return 0; 632 | } 633 | ``` 634 | 635 | ### Signal chaining 636 | 637 | The freestanding `sigslot::connect()` function can be used to connect a signal 638 | to another with compatible arguments. 639 | 640 | ```cpp 641 | #include 642 | #include 643 | 644 | int main() { 645 | sigslot::signal sig1; 646 | sigslot::signal sig2; 647 | 648 | sigslot::connect(sig1, sig2); 649 | sigslot::connect(sig2, [] (double d) { std::cout << "got " << d << std::endl; }); 650 | sig(1); 651 | 652 | return 0; 653 | } 654 | ``` 655 | 656 | ### Thread safety 657 | 658 | Thread safety is unit-tested. In particular, cross-signal emission and recursive 659 | emission run fine in a multiple threads scenario. 660 | 661 | `sigslot::signal` is a typedef to the more general `sigslot::signal_base` template 662 | class, whose first template argument must be a Lockable type. This type will dictate 663 | the locking policy of the class. 664 | 665 | Sigslot offers 2 typedefs, 666 | 667 | - `sigslot::signal` usable from multiple threads and uses std::mutex as a lockable. 668 | In particular, connection, disconnection, emission and slot execution are thread 669 | safe. It is also safe with recursive signal emission. 670 | - `sigslot::signal_st` is a non thread-safe alternative, it trades safety for slightly 671 | faster operation. 672 | 673 | 674 | ## Implementation details 675 | 676 | ### Using function pointers to disconnect slots 677 | 678 | Comparing function pointers is a nightmare in C++. Here is a table demonstrating 679 | the size and address of a variety of cases as a showcase: 680 | 681 | ```cpp 682 | void fun() {} 683 | 684 | struct b1 { 685 | virtual ~b1() = default; 686 | static void sm() {} 687 | void m() {} 688 | virtual void vm() {} 689 | }; 690 | 691 | struct b2 { 692 | virtual ~b2() = default; 693 | static void sm() {} 694 | void m() {} 695 | virtual void vm() {} 696 | }; 697 | 698 | struct c { 699 | virtual ~c() = default; 700 | virtual void w() {} 701 | }; 702 | 703 | struct d : b1 { 704 | static void sm() {} 705 | void m() {} 706 | void vm() override {} 707 | }; 708 | 709 | struct e : b1, c { 710 | static void sm() {} 711 | void m() {} 712 | void vm() override{} 713 | }; 714 | ``` 715 | 716 | | Symbol | GCC 9 Linux 64
Sizeof | GCC 9 Linux 64
Address | MSVC 16.6 32
Sizeof | MSVC 16.6 32
Address | GCC 8 Mingw 32
Sizeof | GCC 8 Mingw 32
Address | Clang-cl 9 32
Sizeof | Clang-cl 9 32
Address | 717 | |---------|--------------------------|---------------------------|------------------------|-------------------------|--------------------------|---------------------------|-------------------------|--------------------------| 718 | | fun | 8 | 0x802340 | 4 | 0x1311A6 | 4 | 0xF41540 | 4 | 0x0010AE | 719 | | &b1::sm | 8 | 0xE03140 | 4 | 0x7612A5 | 4 | 0x308D40 | 4 | 0x0010AE | 720 | | &b1::m | 16 | 0xF03240 | 4 | 0x1514A5 | 8 | 0x248D40 | 4 | 0x0010AE | 721 | | &b1::vm | 16 | 0x11 | 4 | 0x9F11A5 | 8 | 0x09 | 4 | 0x8023AE | 722 | | &b2::sm | 8 | 0x003340 | 4 | 0xA515A5 | 4 | 0x408D40 | 4 | 0x0010AE | 723 | | &b2::m | 16 | 0x103440 | 4 | 0xEB10A5 | 8 | 0x348D40 | 4 | 0x0010AE | 724 | | &b2::vm | 16 | 0x11 | 4 | 0x6A14A5 | 8 | 0x09 | 4 | 0x8023AE | 725 | | &d::sm | 8 | 0x203440 | 4 | 0x2612A5 | 4 | 0x108D40 | 4 | 0x0010AE | 726 | | &d::m | 16 | 0x303540 | 4 | 0x9D13A5 | 8 | 0x048D40 | 4 | 0x0010AE | 727 | | &d::vm | 16 | 0x11 | 4 | 0x4412A5 | 8 | 0x09 | 4 | 0x8023AE | 728 | | &e::sm | 8 | 0x403540 | 4 | 0xF911A5 | 4 | 0x208D40 | 4 | 0x0010AE | 729 | | &e::m | 16 | 0x503640 | 8 | 0x8111A5 | 8 | 0x148D40 | 8 | 0x0010AE | 730 | | &e::vm | 16 | 0x11 | 8 | 0xA911A5 | 8 | 0x09 | 8 | 0x8023AE | 731 | 732 | MSVC and Clang-cl in Release mode optimize functions with the same definition by 733 | merging them. This is a behaviour that can be deactivated with the `/OPT:NOICF` 734 | linker option. 735 | Sigslot tests and examples rely on a lot a identical callables which trigger this 736 | behaviour, which is why it deactivates this particular optimization on the affected 737 | compilers. 738 | 739 | ### Known bugs 740 | 741 | Using generic lambdas with GCC less than version 7.4 can trigger [Bug #68071](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=68071). 742 | 743 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_target(sigslot-tests 2 | COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure 3 | COMMENT "Build and run all the unit tests.") 4 | 5 | macro(pal_create_test target ut) 6 | add_executable(${target} EXCLUDE_FROM_ALL "${ut}") 7 | add_test(${target} ${target}) 8 | add_dependencies(sigslot-tests ${target}) 9 | sigslot_set_properties(${target} PRIVATE) 10 | target_link_libraries(${target} PRIVATE Pal::Sigslot) 11 | target_compile_definitions(${target} PRIVATE $<$:_SCL_SECURE_NO_WARNINGS>) 12 | endmacro() 13 | 14 | file(GLOB_RECURSE UNIT_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.cpp") 15 | foreach(ut IN LISTS UNIT_TESTS) 16 | string(REPLACE ".cpp" "" target ${ut}) 17 | string(REGEX REPLACE "/" "." target ${target}) 18 | set(target sigslot-test-${target}) 19 | 20 | if (target MATCHES "boost") 21 | if (TARGET Boost::system) 22 | pal_create_test(${target} "${ut}") 23 | target_link_libraries(${target} PRIVATE Boost::system) 24 | endif() 25 | elseif (target MATCHES "qt") 26 | if (TARGET Qt5::Core) 27 | pal_create_test(${target} "${ut}") 28 | target_link_libraries(${target} PRIVATE Qt5::Core) 29 | set_target_properties(${target} PROPERTIES AUTOMOC ON) 30 | endif() 31 | else() 32 | pal_create_test(${target} "${ut}") 33 | endif() 34 | endforeach() 35 | -------------------------------------------------------------------------------- /test/bench-slots.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | 5 | static constexpr sigslot::group_id grps = 30; 6 | static constexpr int64_t slts = 3; 7 | static constexpr int64_t emissions = 10000; 8 | static constexpr int64_t runs = 1000; 9 | 10 | static void fun(int64_t &i) { i++; } 11 | 12 | static void test_groups(int64_t &i) { 13 | sigslot::signal sig; 14 | 15 | for (int64_t s = 0; s < slts; ++s) { 16 | for (sigslot::group_id g = 0; g < grps; ++g) { 17 | sig.connect(fun, grps-g); 18 | } 19 | } 20 | 21 | for (int64_t e = 0; e < emissions; ++e) { 22 | sig(i); 23 | } 24 | } 25 | 26 | int main(int, char **) { 27 | int64_t i = 0; 28 | 29 | for (int64_t r = 0; r < runs; ++r) { 30 | test_groups(i); 31 | } 32 | 33 | assert(i == grps * slts * emissions * runs); 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /test/boost-tracking.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | static int sum = 0; 8 | 9 | void f1(int i) { sum += i; } 10 | struct o1 { void operator()(int i) { sum += 2*i; } }; 11 | 12 | struct s { 13 | void f1(int i) { sum += i; } 14 | void f2(int i) const { sum += 2*i; } 15 | }; 16 | 17 | struct dummy {}; 18 | 19 | 20 | void test_track_shared() { 21 | sum = 0; 22 | sigslot::signal sig; 23 | 24 | auto s1 = boost::make_shared(); 25 | sig.connect(&s::f1, s1); 26 | 27 | auto s2 = boost::make_shared(); 28 | boost::weak_ptr w2 = s2; 29 | sig.connect(&s::f2, w2); 30 | 31 | sig(1); 32 | assert(sum == 3); 33 | 34 | s1.reset(); 35 | sig(1); 36 | assert(sum == 5); 37 | 38 | s2.reset(); 39 | sig(1); 40 | assert(sum == 5); 41 | } 42 | 43 | void test_track_other() { 44 | sum = 0; 45 | sigslot::signal sig; 46 | 47 | auto d1 = boost::make_shared(); 48 | sig.connect(f1, d1); 49 | 50 | auto d2 = boost::make_shared(); 51 | boost::weak_ptr w2 = d2; 52 | sig.connect(o1(), w2); 53 | 54 | sig(1); 55 | assert(sum == 3); 56 | 57 | d1.reset(); 58 | sig(1); 59 | assert(sum == 5); 60 | 61 | d2.reset(); 62 | sig(1); 63 | assert(sum == 5); 64 | } 65 | 66 | int main() { 67 | test_track_shared(); 68 | test_track_other(); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /test/function-traits.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | 5 | using namespace sigslot::trait; 6 | 7 | void f1(int, char, float) {} 8 | void f2(int, char, float) noexcept {} 9 | 10 | void f(int) {} 11 | void f(int, char, float) {} 12 | 13 | struct oo { 14 | void operator()(int) {} 15 | void operator()(int, char, float) {} 16 | }; 17 | 18 | struct s { 19 | static void s1(int, char, float) {} 20 | static void s2(int, char, float) noexcept {} 21 | 22 | void f1(int, char, float) {} 23 | void f2(int, char, float) const {} 24 | void f3(int, char, float) volatile {} 25 | void f4(int, char, float) const volatile {} 26 | void f5(int, char, float) noexcept {} 27 | void f6(int, char, float) const noexcept {} 28 | void f7(int, char, float) volatile noexcept {} 29 | void f8(int, char, float) const volatile noexcept {} 30 | }; 31 | 32 | struct o1 { void operator()(int, char, float) {} }; 33 | struct o2 { void operator()(int, char, float) const {} }; 34 | struct o3 { void operator()(int, char, float) volatile {} }; 35 | struct o4 { void operator()(int, char, float) const volatile {} }; 36 | struct o5 { void operator()(int, char, float) noexcept {} }; 37 | struct o6 { void operator()(int, char, float) const noexcept {} }; 38 | struct o7 { void operator()(int, char, float) volatile noexcept {} }; 39 | struct o8 { void operator()(int, char, float) const volatile noexcept {} }; 40 | 41 | using t = typelist; 42 | 43 | static_assert(is_callable_v, ""); 44 | static_assert(is_callable_v, ""); 45 | static_assert(is_callable_v, ""); 46 | static_assert(is_callable_v, ""); 47 | static_assert(is_callable_v, ""); 48 | static_assert(is_callable_v, ""); 49 | static_assert(is_callable_v, ""); 50 | static_assert(is_callable_v, ""); 51 | static_assert(is_callable_v, ""); 52 | static_assert(is_callable_v, ""); 53 | static_assert(is_callable_v, ""); 54 | static_assert(is_callable_v, ""); 55 | static_assert(is_callable_v, ""); 56 | static_assert(is_callable_v, ""); 57 | static_assert(is_callable_v, ""); 58 | static_assert(is_callable_v, ""); 59 | static_assert(is_callable_v, ""); 60 | static_assert(is_callable_v, ""); 61 | static_assert(is_callable_v, ""); 62 | static_assert(is_callable_v, ""); 63 | static_assert(is_callable_v, ""); 64 | 65 | int main() { 66 | auto l1 = [](int, char, float) {}; 67 | auto l2 = [&](int, char, float) mutable {}; 68 | auto l3 = [&](auto...) mutable {}; 69 | 70 | static_assert(is_callable_v, ""); 71 | static_assert(is_callable_v, ""); 72 | static_assert(is_callable_v, ""); 73 | 74 | return 0; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /test/observer.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct s : ::sigslot::observer { 9 | ~s() override { 10 | this->disconnect_all(); 11 | } 12 | 13 | void f1 (int &i) { ++i; } 14 | }; 15 | 16 | struct s_st : ::sigslot::observer_st { 17 | void f1 (int &i) { ++i; } 18 | }; 19 | 20 | struct s_plain { 21 | void f1 (int &i) { ++i; } 22 | }; 23 | 24 | template class SIG_T> 25 | void test_observer() { 26 | SIG_T sig; 27 | 28 | // Automatic disconnect via observer inheritance 29 | { 30 | T p1; 31 | sig.connect(&T::f1, &p1); 32 | assert(sig.slot_count() == 1); 33 | 34 | { 35 | T p2; 36 | sig.connect(&T::f1, &p2); 37 | assert(sig.slot_count() == 2); 38 | } 39 | 40 | assert(sig.slot_count() == 1); 41 | } 42 | 43 | assert(sig.slot_count() == 0); 44 | 45 | // No automatic disconnect 46 | { 47 | s_plain p; 48 | sig.connect(&s_plain::f1, &p); 49 | assert(sig.slot_count() == 1); 50 | } 51 | 52 | assert(sig.slot_count() == 1); 53 | } 54 | 55 | template class SIG_T> 56 | void test_observer_signals() { 57 | int sum = 0; 58 | SIG_T sig; 59 | 60 | { 61 | T p1; 62 | sig.connect(&T::f1, &p1); 63 | sig(sum); 64 | assert(sum == 1); 65 | { 66 | T p2; 67 | sig.connect(&T::f1, &p2); 68 | sig(sum); 69 | assert(sum == 3); 70 | } 71 | sig(sum); 72 | assert(sum == 4); 73 | } 74 | 75 | sig(sum); 76 | assert(sum == 4); 77 | } 78 | 79 | template class SIG_T> 80 | void test_observer_signals_heap() { 81 | int sum = 0; 82 | SIG_T sig; 83 | 84 | { 85 | auto *p1 = new T; 86 | sig.connect(&T::f1, p1); 87 | sig(sum); 88 | assert(sum == 1); 89 | { 90 | auto *p2 = new T; 91 | sig.connect(&T::f1, p2); 92 | sig(sum); 93 | assert(sum == 3); 94 | delete p2; 95 | } 96 | sig(sum); 97 | assert(sum == 4); 98 | delete p1; 99 | } 100 | 101 | sig(sum); 102 | assert(sum == 4); 103 | } 104 | 105 | 106 | template class SIG_T> 107 | void test_observer_signals_shared() { 108 | int sum = 0; 109 | SIG_T sig; 110 | 111 | { 112 | auto p1 = std::make_shared(); 113 | sig.connect(&T::f1, p1); 114 | sig(sum); 115 | assert(sum == 1); 116 | { 117 | auto p2 = std::make_shared(); 118 | sig.connect(&T::f1, p2); 119 | sig(sum); 120 | assert(sum == 3); 121 | } 122 | sig(sum); 123 | assert(sum == 4); 124 | } 125 | 126 | sig(sum); 127 | assert(sum == 4); 128 | } 129 | 130 | template class SIG_T> 131 | void test_observer_signals_list() { 132 | int sum = 0; 133 | SIG_T sig; 134 | 135 | { 136 | std::list l; 137 | for (auto i = 0; i < 10; ++i) 138 | { 139 | l.emplace_back(); 140 | sig.connect(&T::f1, &l.back()); 141 | } 142 | 143 | assert(sig.slot_count() == 10); 144 | sig(sum); 145 | assert(sum == 10); 146 | } 147 | 148 | assert(sig.slot_count() == 0); 149 | sig(sum); 150 | assert(sum == 10); 151 | } 152 | 153 | template class SIG_T> 154 | void test_observer_signals_vector() { 155 | int sum = 0; 156 | SIG_T sig; 157 | 158 | { 159 | std::vector> v; 160 | for (auto i = 0; i < 10; ++i) 161 | { 162 | v.emplace_back(new T); 163 | sig.connect(&T::f1, v.back().get()); 164 | } 165 | assert(sig.slot_count() == 10); 166 | sig(sum); 167 | assert(sum == 10); 168 | } 169 | 170 | assert(sig.slot_count() == 0); 171 | sig(sum); 172 | assert(sum == 10); 173 | } 174 | 175 | int main() 176 | { 177 | test_observer(); 178 | test_observer(); 179 | test_observer_signals(); 180 | test_observer_signals(); 181 | test_observer_signals_heap(); 182 | test_observer_signals_heap(); 183 | test_observer_signals_shared(); 184 | test_observer_signals_shared(); 185 | test_observer_signals_list(); 186 | test_observer_signals_list(); 187 | test_observer_signals_vector(); 188 | test_observer_signals_vector(); 189 | return 0; 190 | } 191 | -------------------------------------------------------------------------------- /test/pmf-disconnection.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | // Test of complex disconnection of pointer to member function scenarii, 8 | // to ensure it copes well with any function type. 9 | 10 | static int sum = 0; 11 | 12 | struct b1 { 13 | virtual ~b1() = default; 14 | static void sm() { sum++; } 15 | void m() { sum++; } 16 | virtual void vm() { sum++; } 17 | }; 18 | 19 | struct b2 { 20 | virtual ~b2() = default; 21 | static void sm() { sum++; } 22 | void m() { sum++; } 23 | virtual void vm() { sum++; } 24 | }; 25 | 26 | struct c { 27 | virtual ~c() = default; 28 | virtual void w() {} 29 | }; 30 | 31 | struct d : b1 { 32 | static void sm() { sum++; } 33 | void m() { sum++; } 34 | void vm() override { sum++; } 35 | }; 36 | 37 | struct e : b1, c { 38 | static void sm() { sum++; } 39 | void m() const { sum++; } 40 | void vm() override { sum++; } 41 | }; 42 | 43 | struct f : virtual b1 { 44 | static void sm() { sum++; } 45 | void m() const { sum++; } 46 | void vm() override { sum++; } 47 | }; 48 | 49 | int main(int, char **) { 50 | sigslot::signal<> sig; 51 | 52 | auto sb1 = std::make_shared(); 53 | auto sb2 = std::make_shared(); 54 | auto sd = std::make_shared(); 55 | auto se = std::make_shared(); 56 | auto sf = std::make_shared(); 57 | 58 | sig.connect(&b1::sm); 59 | sig.connect(&b1::m, sb1); 60 | sig.connect(&b1::vm, sb1); 61 | sig.connect(&b2::sm); 62 | sig.connect(&b2::m, sb2); 63 | sig.connect(&b2::vm, sb2); 64 | sig.connect(&d::sm); 65 | sig.connect(&d::m, sd); 66 | sig.connect(&d::vm, sd); 67 | sig.connect(&e::sm); 68 | sig.connect(&e::m, se); 69 | sig.connect(&e::vm, se); 70 | sig.connect(&f::sm); 71 | sig.connect(&f::m, sf); 72 | sig.connect(&f::vm, sf); 73 | 74 | sig(); 75 | assert(sum == 15); 76 | 77 | #ifdef SIGSLOT_RTTI_ENABLED 78 | size_t n = 0; 79 | n = sig.disconnect(&b1::sm); 80 | assert(n == 1); 81 | n = sig.disconnect(&b1::m); 82 | assert(n == 1); 83 | n = sig.disconnect(&b1::vm); 84 | assert(n == 1); 85 | n = sig.disconnect(&b2::sm); 86 | assert(n == 1); 87 | n = sig.disconnect(&b2::m); 88 | assert(n == 1); 89 | n = sig.disconnect(&b2::vm); 90 | assert(n == 1); 91 | n = sig.disconnect(&d::sm); 92 | assert(n == 1); 93 | n = sig.disconnect(&d::m); 94 | assert(n == 1); 95 | n = sig.disconnect(&d::vm); 96 | assert(n == 1); 97 | n = sig.disconnect(&e::sm); 98 | assert(n == 1); 99 | n = sig.disconnect(&e::m); 100 | assert(n == 1); 101 | n = sig.disconnect(&e::vm); 102 | assert(n == 1); 103 | n = sig.disconnect(&f::sm); 104 | assert(n == 1); 105 | n = sig.disconnect(&f::m); 106 | assert(n == 1); 107 | n = sig.disconnect(&f::vm); 108 | assert(n == 1); 109 | #endif 110 | 111 | return 0; 112 | } 113 | -------------------------------------------------------------------------------- /test/qt-tracking.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | #include 5 | 6 | static int sum = 0; 7 | 8 | void f1(int i) { sum += i; } 9 | struct o1 { void operator()(int i) { sum += 2*i; } }; 10 | 11 | struct s { 12 | void f1(int i) { sum += i; } 13 | void f2(int i) const { sum += 2*i; } 14 | }; 15 | 16 | struct dummy {}; 17 | 18 | class MyObject : public QObject { 19 | Q_OBJECT 20 | 21 | public: 22 | MyObject(QObject *parent = nullptr) : QObject(parent) {} 23 | 24 | public slots: 25 | void addSum(int i) const { sum += i; } 26 | }; 27 | 28 | #include "qt-tracking.moc" 29 | 30 | void test_track_shared() { 31 | sum = 0; 32 | sigslot::signal sig; 33 | 34 | auto s1 = QSharedPointer::create(); 35 | sig.connect(&s::f1, s1); 36 | 37 | auto s2 = QSharedPointer::create(); 38 | auto w2 = s2.toWeakRef(); 39 | sig.connect(&s::f2, w2); 40 | 41 | sig(1); 42 | assert(sum == 3); 43 | 44 | s1.clear(); 45 | sig(1); 46 | assert(sum == 5); 47 | 48 | s2.clear(); 49 | sig(1); 50 | assert(sum == 5); 51 | } 52 | 53 | void test_track_shared_other() { 54 | sum = 0; 55 | sigslot::signal sig; 56 | 57 | auto d1 = QSharedPointer::create(); 58 | sig.connect(f1, d1); 59 | 60 | auto d2 = QSharedPointer::create(); 61 | auto w2 = d2.toWeakRef(); 62 | sig.connect(o1(), w2); 63 | 64 | sig(1); 65 | assert(sum == 3); 66 | 67 | d1.reset(); 68 | sig(1); 69 | assert(sum == 5); 70 | 71 | d2.reset(); 72 | sig(1); 73 | assert(sum == 5); 74 | } 75 | 76 | void test_track_qobject() { 77 | sum = 0; 78 | sigslot::signal sig; 79 | 80 | { 81 | MyObject o; 82 | sig.connect(&MyObject::addSum, &o); 83 | 84 | sig(1); 85 | assert(sum == 1); 86 | } 87 | 88 | sig(1); 89 | assert(sum == 1); 90 | } 91 | 92 | void test_track_qobject_other() { 93 | sum = 0; 94 | sigslot::signal sig; 95 | 96 | { 97 | MyObject o; 98 | sig.connect(f1, &o); 99 | 100 | sig(1); 101 | assert(sum == 1); 102 | } 103 | 104 | sig(1); 105 | assert(sum == 1); 106 | } 107 | 108 | int main() { 109 | test_track_shared(); 110 | test_track_shared_other(); 111 | test_track_qobject(); 112 | test_track_qobject_other(); 113 | return 0; 114 | } 115 | -------------------------------------------------------------------------------- /test/recursive.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | 5 | template 6 | struct object { 7 | object(T i) : v{i} {} 8 | 9 | void inc_val(const T &i) { 10 | if (i != v) { 11 | v++; 12 | sig(v); 13 | } 14 | } 15 | 16 | void dec_val(const T &i) { 17 | if (i != v) { 18 | v--; 19 | sig(v); 20 | } 21 | } 22 | 23 | T v; 24 | sigslot::signal sig; 25 | }; 26 | 27 | void test_recursive() { 28 | object i1(-1); 29 | object i2(10); 30 | 31 | i1.sig.connect(&object::dec_val, &i2); 32 | i2.sig.connect(&object::inc_val, &i1); 33 | 34 | i1.inc_val(0); 35 | 36 | assert(i1.v == i2.v); 37 | } 38 | 39 | void test_self_recursive() { 40 | int i = 0; 41 | 42 | sigslot::signal s; 43 | s.connect([&] (int v) { 44 | if (i < 10) { 45 | i++; 46 | s(v+1); 47 | } 48 | }); 49 | 50 | s(0); 51 | 52 | assert(i == 10); 53 | } 54 | 55 | int main() { 56 | test_recursive(); 57 | test_self_recursive(); 58 | return 0; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /test/signal-extended.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | 5 | static int sum = 0; 6 | 7 | void f(sigslot::connection &c, int i) { sum += i; c.disconnect(); } 8 | 9 | struct s { 10 | static void sf(sigslot::connection &c, int i) { sum += i; c.disconnect(); } 11 | void f(sigslot::connection &c, int i) { sum += i; c.disconnect(); } 12 | }; 13 | 14 | struct o { 15 | void operator()(sigslot::connection &c, int i) { sum += i; c.disconnect(); } 16 | }; 17 | 18 | void test_free_connection() { 19 | sum = 0; 20 | sigslot::signal sig; 21 | sig.connect_extended(f); 22 | 23 | sig(1); 24 | assert(sum == 1); 25 | sig(1); 26 | assert(sum == 1); 27 | } 28 | 29 | void test_static_connection() { 30 | sum = 0; 31 | sigslot::signal sig; 32 | sig.connect_extended(&s::sf); 33 | 34 | sig(1); 35 | assert(sum == 1); 36 | sig(1); 37 | assert(sum == 1); 38 | } 39 | 40 | void test_pmf_connection() { 41 | sum = 0; 42 | sigslot::signal sig; 43 | s p; 44 | sig.connect_extended(&s::f, &p); 45 | 46 | sig(1); 47 | assert(sum == 1); 48 | sig(1); 49 | assert(sum == 1); 50 | } 51 | 52 | void test_function_object_connection() { 53 | sum = 0; 54 | sigslot::signal sig; 55 | sig.connect_extended(o{}); 56 | 57 | sig(1); 58 | assert(sum == 1); 59 | sig(1); 60 | assert(sum == 1); 61 | } 62 | 63 | void test_lambda_connection() { 64 | sum = 0; 65 | sigslot::signal sig; 66 | 67 | sig.connect_extended([&](sigslot::connection &c, int i) { 68 | sum += i; 69 | c.disconnect(); 70 | }); 71 | sig(1); 72 | assert(sum == 1); 73 | 74 | sig.connect_extended([&](sigslot::connection &c, int i) mutable { 75 | sum += 2*i; 76 | c.disconnect(); 77 | }); 78 | sig(1); 79 | assert(sum == 3); 80 | sig(1); 81 | assert(sum == 3); 82 | } 83 | 84 | int main() { 85 | test_free_connection(); 86 | test_static_connection(); 87 | test_pmf_connection(); 88 | test_function_object_connection(); 89 | test_lambda_connection(); 90 | } 91 | -------------------------------------------------------------------------------- /test/signal-performance.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void test_signal_performance() { 9 | using Clock = std::chrono::high_resolution_clock; 10 | using TimePoint = std::chrono::time_point; 11 | 12 | constexpr int count = 1000; 13 | double ref_ns = 0.; 14 | sigslot::signal<> sig; 15 | { 16 | std::vector connections; 17 | connections.reserve(count); 18 | for (int i = 0; i < count; i++) { 19 | connections.emplace_back(sig.connect([]{})); 20 | } 21 | 22 | // Measure first signal time as reference 23 | const TimePoint begin = Clock::now(); 24 | sig(); 25 | ref_ns = double(std::chrono::duration_cast(Clock::now() - begin).count()); 26 | } 27 | 28 | // Measure signal after all slot were disconnected 29 | const TimePoint begin = Clock::now(); 30 | sig(); 31 | const double after_disconnection_ns = double(std::chrono::duration_cast(Clock::now() - begin).count()); 32 | 33 | // Ensure that the signal cost is not > at 10% 34 | const auto max_delta = 0.1; 35 | const auto delta = (after_disconnection_ns - ref_ns) / ref_ns; 36 | 37 | std::cout << "ref: " << ref_ns / 1000 << " us" << std::endl; 38 | std::cout << "after: " << after_disconnection_ns / 1000 << " us" << std::endl; 39 | std::cout << "delta: " << delta << " SU" << std::endl; 40 | 41 | assert(delta < max_delta); 42 | } 43 | 44 | int main() { 45 | test_signal_performance(); 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /test/signal-pmf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // A program that computes the function pointer size and uniqueness for a variety 8 | // of cases. 9 | 10 | void fun() {} 11 | 12 | struct b1 { 13 | virtual ~b1() = default; 14 | static void sm() {} 15 | void m() {} 16 | virtual void vm() {} 17 | }; 18 | 19 | struct b2 { 20 | virtual ~b2() = default; 21 | static void sm() {} 22 | void m() {} 23 | virtual void vm() {} 24 | }; 25 | 26 | struct c { 27 | virtual ~c() = default; 28 | virtual void w() {} 29 | }; 30 | 31 | struct d : b1 { 32 | static void sm() {} 33 | void m() {} 34 | void vm() override {} 35 | }; 36 | 37 | struct e : b1, c { 38 | static void sm() {} 39 | void m() {} 40 | void vm() override{} 41 | }; 42 | 43 | template 44 | union sizer { 45 | T t; 46 | char data[sizeof(T)]; 47 | }; 48 | 49 | template 50 | std::string ptr_string(const T&t) { 51 | sizer ss; 52 | std::uninitialized_fill(std::begin(ss.data), std::end(ss.data), '\0'); 53 | ss.t = t; 54 | std::string addr; 55 | for (char i : ss.data) { 56 | char b[] = "00"; 57 | std::snprintf(b, 3, "%02X", static_cast(i)); 58 | addr += b; 59 | } 60 | 61 | // shorten string 62 | while (addr.size() >= 2) { 63 | auto si = addr.size(); 64 | if (addr[si-1] == '0' && addr[si-2] == '0') { 65 | addr.pop_back(); 66 | addr.pop_back(); 67 | } else { 68 | break; 69 | } 70 | } 71 | return addr; 72 | } 73 | 74 | template 75 | std::string print(std::string name, const T&t) { 76 | auto addr = ptr_string(t); 77 | std::cout << name << "\t" << sizeof(t) << "\t0x" << addr << std::endl; 78 | return addr; 79 | } 80 | 81 | int main(int, char **) { 82 | std::vector addrs; 83 | 84 | addrs.push_back(print("fun", &fun)); 85 | addrs.push_back(print("&b1::sm", &b1::sm)); 86 | addrs.push_back(print("&b1::m", &b1::m)); 87 | addrs.push_back(print("&b1::vm", &b1::vm)); 88 | addrs.push_back(print("&b2::sm", &b2::sm)); 89 | addrs.push_back(print("&b2::m", &b2::m)); 90 | addrs.push_back(print("&b2::vm", &b2::vm)); 91 | addrs.push_back(print("&d::sm", &d::sm)); 92 | addrs.push_back(print("&d::m", &d::m)); 93 | addrs.push_back(print("&d::vm", &d::vm)); 94 | addrs.push_back(print("&e::sm", &e::sm)); 95 | addrs.push_back(print("&e::m", &e::m)); 96 | addrs.push_back(print("&e::vm", &e::vm)); 97 | 98 | std::sort(addrs.begin(), addrs.end()); 99 | auto last = std::unique(addrs.begin(), addrs.end()); 100 | std::cout << "Address duplicates: " 101 | << std::distance(last, addrs.end()) << std::endl; 102 | return 0; 103 | } 104 | -------------------------------------------------------------------------------- /test/signal-threaded.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static std::atomic sum{0}; 9 | 10 | static void f(int i) { sum += i; } 11 | static void f1(int i) { sum += i; } 12 | static void f2(int i) { sum += i; } 13 | static void f3(int i) { sum += i; } 14 | 15 | static void emit_many(sigslot::signal &sig) { 16 | for (int i = 0; i < 10000; ++i) 17 | sig(1); 18 | } 19 | 20 | static void connect_emit(sigslot::signal &sig) { 21 | for (int i = 0; i < 100; ++i) { 22 | auto s = sig.connect_scoped(f); 23 | for (int j = 0; j < 100; ++j) 24 | sig(1); 25 | } 26 | } 27 | 28 | static void connect_cross(sigslot::signal &s1, 29 | sigslot::signal &s2, 30 | std::atomic &go) 31 | { 32 | auto cross = s1.connect([&](int i) { 33 | if (i & 1) 34 | f(i); 35 | else 36 | s2(i+1); 37 | }); 38 | 39 | go++; 40 | while (go != 3) 41 | std::this_thread::yield(); 42 | 43 | for (int i = 0; i < 1000000; ++i) 44 | s1(i); 45 | } 46 | 47 | static void test_threaded_mix() { 48 | sum = 0; 49 | 50 | sigslot::signal sig; 51 | 52 | std::array threads; 53 | for (auto &t : threads) 54 | t = std::thread(connect_emit, std::ref(sig)); 55 | 56 | for (auto &t : threads) 57 | t.join(); 58 | } 59 | 60 | static void test_threaded_emission() { 61 | sum = 0; 62 | 63 | sigslot::signal sig; 64 | sig.connect(f); 65 | 66 | std::array threads; 67 | for (auto &t : threads) 68 | t = std::thread(emit_many, std::ref(sig)); 69 | 70 | for (auto &t : threads) 71 | t.join(); 72 | 73 | assert(sum == 100000l); 74 | } 75 | 76 | // test for deadlocks in cross emission situation 77 | static void test_threaded_crossed() { 78 | sum = 0; 79 | 80 | sigslot::signal sig1; 81 | sigslot::signal sig2; 82 | 83 | std::atomic go{0}; 84 | 85 | std::thread t1(connect_cross, std::ref(sig1), std::ref(sig2), std::ref(go)); 86 | std::thread t2(connect_cross, std::ref(sig2), std::ref(sig1), std::ref(go)); 87 | 88 | while (go != 2) 89 | std::this_thread::yield(); 90 | go++; 91 | 92 | t1.join(); 93 | t2.join(); 94 | 95 | assert(sum == std::int64_t(1000000000000ll)); 96 | } 97 | 98 | // test what happens when more than one thread attempt disconnection 99 | static void test_threaded_misc() { 100 | sum = 0; 101 | sigslot::signal sig; 102 | std::atomic run{true}; 103 | 104 | auto emitter = [&] { 105 | while (run) { 106 | sig(1); 107 | } 108 | }; 109 | 110 | auto conn = [&] { 111 | while (run) { 112 | for (int i = 0; i < 10; ++i) { 113 | sig.connect(f1); 114 | sig.connect(f2); 115 | sig.connect(f3); 116 | } 117 | } 118 | }; 119 | 120 | auto disconn = [&] { 121 | unsigned int i = 0; 122 | while (run) { 123 | if (i == 0) 124 | sig.disconnect(f1); 125 | else if (i == 1) 126 | sig.disconnect(f2); 127 | else 128 | sig.disconnect(f3); 129 | i++; 130 | i = i % 3; 131 | } 132 | }; 133 | 134 | std::array emitters; 135 | std::array conns; 136 | std::array disconns; 137 | 138 | for (auto &t :conns) 139 | t = std::thread(conn); 140 | for (auto &t : emitters) 141 | t = std::thread(emitter); 142 | for (auto &t : disconns) 143 | t = std::thread(disconn); 144 | 145 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 146 | run = false; 147 | 148 | for (auto &t : emitters) 149 | t.join(); 150 | for (auto &t : disconns) 151 | t.join(); 152 | for (auto &t : conns) 153 | t.join(); 154 | } 155 | 156 | int main() { 157 | test_threaded_emission(); 158 | test_threaded_mix(); 159 | test_threaded_crossed(); 160 | test_threaded_misc(); 161 | 162 | return 0; 163 | } 164 | -------------------------------------------------------------------------------- /test/signal-tracking.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static int sum = 0; 9 | 10 | void f1(int i) { sum += i; } 11 | struct o1 { void operator()(int i) { sum += 2*i; } }; 12 | 13 | struct s { 14 | void f1(int i) { sum += i; } 15 | void f2(int i) const { sum += 2*i; } 16 | }; 17 | 18 | struct oo { 19 | void operator()(int i) { sum += i; } 20 | void operator()(double i) { sum += static_cast(std::round(4*i)); } 21 | }; 22 | 23 | struct dummy {}; 24 | 25 | static_assert(sigslot::trait::is_callable_v, decltype(&s::f1), std::shared_ptr>, ""); 26 | 27 | void test_track_shared() { 28 | sum = 0; 29 | sigslot::signal sig; 30 | 31 | auto s1 = std::make_shared(); 32 | auto conn1 = sig.connect(&s::f1, s1); 33 | 34 | auto s2 = std::make_shared(); 35 | std::weak_ptr w2 = s2; 36 | auto conn2 = sig.connect(&s::f2, w2); 37 | 38 | sig(1); 39 | assert(sum == 3); 40 | 41 | s1.reset(); 42 | sig(1); 43 | assert(sum == 5); 44 | assert(!conn1.valid()); 45 | 46 | s2.reset(); 47 | sig(1); 48 | assert(sum == 5); 49 | assert(!conn2.valid()); 50 | } 51 | 52 | // bug #2 remove last slot first 53 | void test_track_shared_reversed() { 54 | sum = 0; 55 | sigslot::signal sig; 56 | 57 | auto s1 = std::make_shared(); 58 | auto conn1 = sig.connect(&s::f1, s1); 59 | 60 | auto s2 = std::make_shared(); 61 | std::weak_ptr w2 = s2; 62 | auto conn2 = sig.connect(&s::f2, w2); 63 | 64 | sig(1); 65 | assert(sum == 3); 66 | 67 | s2.reset(); 68 | sig(1); 69 | assert(sum == 4); 70 | assert(!conn2.valid()); 71 | 72 | s1.reset(); 73 | sig(1); 74 | assert(sum == 4); 75 | assert(!conn1.valid()); 76 | } 77 | 78 | void test_track_other() { 79 | sum = 0; 80 | sigslot::signal sig; 81 | 82 | auto d1 = std::make_shared(); 83 | auto conn1 = sig.connect(f1, d1); 84 | 85 | auto d2 = std::make_shared(); 86 | std::weak_ptr w2 = d2; 87 | auto conn2 = sig.connect(o1(), w2); 88 | 89 | sig(1); 90 | assert(sum == 3); 91 | 92 | d1.reset(); 93 | sig(1); 94 | assert(sum == 5); 95 | assert(!conn1.valid()); 96 | 97 | d2.reset(); 98 | sig(1); 99 | assert(sum == 5); 100 | assert(!conn2.valid()); 101 | } 102 | 103 | void test_track_overloaded_function_object() { 104 | sum = 0; 105 | sigslot::signal sig; 106 | sigslot::signal sig1; 107 | 108 | auto d1 = std::make_shared(); 109 | auto conn1 = sig.connect(oo{}, d1); 110 | sig(1); 111 | assert(sum == 1); 112 | 113 | d1.reset(); 114 | sig(1); 115 | assert(sum == 1); 116 | assert(!conn1.valid()); 117 | 118 | auto d2 = std::make_shared(); 119 | std::weak_ptr w2 = d2; 120 | auto conn2 = sig1.connect(oo{}, w2); 121 | sig1(1); 122 | assert(sum == 5); 123 | 124 | d2.reset(); 125 | sig1(1); 126 | assert(sum == 5); 127 | assert(!conn2.valid()); 128 | } 129 | 130 | void test_track_generic_lambda() { 131 | std::stringstream s; 132 | 133 | auto f = [&] (auto a, auto ...args) { 134 | using result_t = int[]; 135 | s << a; 136 | result_t r{ 1, ((void)(s << args), 1)..., }; 137 | (void)r; 138 | }; 139 | 140 | sigslot::signal sig1; 141 | sigslot::signal sig2; 142 | sigslot::signal sig3; 143 | 144 | auto d1 = std::make_shared(); 145 | sig1.connect(f, d1); 146 | sig2.connect(f, d1); 147 | sig3.connect(f, d1); 148 | 149 | sig1(1); 150 | sig2("foo"); 151 | sig3(4.1); 152 | assert(s.str() == "1foo4.1"); 153 | 154 | d1.reset(); 155 | sig1(2); 156 | sig2("bar"); 157 | sig3(3.0); 158 | assert(s.str() == "1foo4.1"); 159 | } 160 | 161 | int main() { 162 | test_track_shared(); 163 | test_track_shared_reversed(); 164 | test_track_other(); 165 | test_track_overloaded_function_object(); 166 | test_track_generic_lambda(); 167 | return 0; 168 | } 169 | -------------------------------------------------------------------------------- /test/signal.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static int sum = 0; 9 | 10 | void f1(int i) { sum += i; } 11 | void f2(int i) noexcept { sum += 2*i; } 12 | 13 | struct s { 14 | static void s1(int i) { sum += i; } 15 | static void s2(int i) noexcept { sum += 2*i; } 16 | 17 | void f1(int i) { sum += i; } 18 | void f2(int i) const { sum += i; } 19 | void f3(int i) volatile { sum += i; } 20 | void f4(int i) const volatile { sum += i; } 21 | void f5(int i) noexcept { sum += i; } 22 | void f6(int i) const noexcept { sum += i; } 23 | void f7(int i) volatile noexcept { sum += i; } 24 | void f8(int i) const volatile noexcept { sum += i; } 25 | }; 26 | 27 | struct oo { 28 | void operator()(int i) { sum += i; } 29 | void operator()(double i) { sum += int(std::round(4*i)); } 30 | }; 31 | 32 | struct o1 { void operator()(int i) { sum += i; } }; 33 | struct o2 { void operator()(int i) const { sum += i; } }; 34 | struct o3 { void operator()(int i) volatile { sum += i; } }; 35 | struct o4 { void operator()(int i) const volatile { sum += i; } }; 36 | struct o5 { void operator()(int i) noexcept { sum += i; } }; 37 | struct o6 { void operator()(int i) const noexcept { sum += i; } }; 38 | struct o7 { void operator()(int i) volatile noexcept { sum += i; } }; 39 | struct o8 { void operator()(int i) const volatile noexcept { sum += i; } }; 40 | 41 | void test_slot_count() { 42 | sigslot::signal sig; 43 | s p; 44 | 45 | sig.connect(&s::f1, &p); 46 | assert(sig.slot_count() == 1); 47 | sig.connect(&s::f2, &p); 48 | assert(sig.slot_count() == 2); 49 | sig.connect(&s::f3, &p); 50 | assert(sig.slot_count() == 3); 51 | sig.connect(&s::f4, &p); 52 | assert(sig.slot_count() == 4); 53 | sig.connect(&s::f5, &p); 54 | assert(sig.slot_count() == 5); 55 | sig.connect(&s::f6, &p); 56 | assert(sig.slot_count() == 6); 57 | 58 | { 59 | sigslot::scoped_connection conn = sig.connect(&s::f7, &p); 60 | assert(sig.slot_count() == 7); 61 | } 62 | assert(sig.slot_count() == 6); 63 | 64 | auto conn = sig.connect(&s::f8, &p); 65 | assert(sig.slot_count() == 7); 66 | conn.disconnect(); 67 | assert(sig.slot_count() == 6); 68 | 69 | sig.disconnect_all(); 70 | assert(sig.slot_count() == 0); 71 | } 72 | 73 | void test_free_connection() { 74 | sum = 0; 75 | sigslot::signal sig; 76 | 77 | auto c1 = sig.connect(f1); 78 | sig(1); 79 | assert(sum == 1); 80 | 81 | sig.connect(f2); 82 | sigslot::connect(sig, f1); 83 | sig(1); 84 | assert(sum == 5); 85 | } 86 | 87 | void test_static_connection() { 88 | sum = 0; 89 | sigslot::signal sig; 90 | 91 | sig.connect(&s::s1); 92 | sig(1); 93 | assert(sum == 1); 94 | 95 | sig.connect(&s::s2); 96 | sigslot::connect(sig, &s::s1); 97 | sig(1); 98 | assert(sum == 5); 99 | } 100 | 101 | void test_pmf_connection() { 102 | sum = 0; 103 | sigslot::signal sig; 104 | s p; 105 | 106 | sig.connect(&s::f1, &p); 107 | sig.connect(&s::f2, &p); 108 | sig.connect(&s::f3, &p); 109 | sig.connect(&s::f4, &p); 110 | sig.connect(&s::f5, &p); 111 | sig.connect(&s::f6, &p); 112 | sig.connect(&s::f7, &p); 113 | sig.connect(&s::f8, &p); 114 | sigslot::connect(sig, &s::f1, &p); 115 | 116 | sig(1); 117 | assert(sum == 9); 118 | } 119 | 120 | void test_const_pmf_connection() { 121 | sum = 0; 122 | sigslot::signal sig; 123 | const s p; 124 | 125 | sig.connect(&s::f2, &p); 126 | sig.connect(&s::f4, &p); 127 | sig.connect(&s::f6, &p); 128 | sig.connect(&s::f8, &p); 129 | sigslot::connect(sig, &s::f2, &p); 130 | 131 | sig(1); 132 | assert(sum == 5); 133 | } 134 | 135 | void test_function_object_connection() { 136 | sum = 0; 137 | sigslot::signal sig; 138 | 139 | sig.connect(o1{}); 140 | sig.connect(o2{}); 141 | sig.connect(o3{}); 142 | sig.connect(o4{}); 143 | sig.connect(o5{}); 144 | sig.connect(o6{}); 145 | sig.connect(o7{}); 146 | sig.connect(o8{}); 147 | sigslot::connect(sig, o1{}); 148 | 149 | sig(1); 150 | assert(sum == 9); 151 | } 152 | 153 | void test_overloaded_function_object_connection() { 154 | sum = 0; 155 | sigslot::signal sig; 156 | sigslot::signal sig1; 157 | 158 | sig.connect(oo{}); 159 | sigslot::connect(sig, oo{}); 160 | sig(1); 161 | assert(sum == 2); 162 | 163 | sig1.connect(oo{}); 164 | sigslot::connect(sig1, oo{}); 165 | sig1(1); 166 | assert(sum == 10); 167 | } 168 | 169 | void test_lambda_connection() { 170 | sum = 0; 171 | sigslot::signal sig; 172 | 173 | sig.connect([&](int i) { sum += i; }); 174 | sigslot::connect(sig, [&](int i) { sum += i; }); 175 | sig(1); 176 | assert(sum == 2); 177 | 178 | sig.connect([&](int i) mutable { sum += 2*i; }); 179 | sigslot::connect(sig, [&](int i) mutable { sum += 2*i; }); 180 | sig(1); 181 | assert(sum == 8); 182 | } 183 | 184 | void test_generic_lambda_connection() { 185 | std::stringstream s; 186 | 187 | auto f = [&] (auto a, auto ...args) { 188 | using result_t = int[]; 189 | s << a; 190 | result_t r{ 1, ((void)(s << args), 1)..., }; 191 | (void)r; 192 | }; 193 | 194 | sigslot::signal sig1; 195 | sigslot::signal sig2; 196 | sigslot::signal sig3; 197 | 198 | sig1.connect(f); 199 | sig2.connect(f); 200 | sig3.connect(f); 201 | sigslot::connect(sig1, f); 202 | sigslot::connect(sig2, f); 203 | sigslot::connect(sig3, f); 204 | sig1(1); 205 | sig2("foo"); 206 | sig3(4.1); 207 | 208 | assert(s.str() == "11foofoo4.14.1"); 209 | } 210 | 211 | void test_lvalue_emission() { 212 | sum = 0; 213 | sigslot::signal sig; 214 | 215 | auto c1 = sig.connect(f1); 216 | int v = 1; 217 | sig(v); 218 | assert(sum == 1); 219 | 220 | sig.connect(f2); 221 | sig(v); 222 | assert(sum == 4); 223 | } 224 | 225 | void test_mutation() { 226 | int res = 0; 227 | sigslot::signal sig; 228 | 229 | sig.connect([](int &r) { r += 1; }); 230 | sig(res); 231 | assert(res == 1); 232 | 233 | sig.connect([](int &r) mutable { r += 2; }); 234 | sig(res); 235 | assert(res == 4); 236 | } 237 | 238 | void test_compatible_args() { 239 | long ll = 0; 240 | std::string ss; 241 | short ii = 0; 242 | 243 | auto f = [&] (long l, const std::string &s, short i) { 244 | ll = l; ss = s; ii = i; 245 | }; 246 | 247 | sigslot::signal sig; 248 | sig.connect(f); 249 | sig('0', "foo", true); 250 | 251 | assert(ll == 48); 252 | assert(ss == "foo"); 253 | assert(ii == 1); 254 | } 255 | 256 | void test_compatible_args_chaining() { 257 | long ll = 0; 258 | std::string ss; 259 | short ii = 0; 260 | 261 | auto f = [&] (long l, const std::string &s, short i) { 262 | ll = l; ss = s; ii = i; 263 | }; 264 | 265 | sigslot::signal sig1; 266 | sig1.connect(f); 267 | 268 | sigslot::signal sig2; 269 | sigslot::connect(sig2, sig1); 270 | sig2('0', "foo", true); 271 | 272 | assert(ll == 48); 273 | assert(ss == "foo"); 274 | assert(ii == 1); 275 | } 276 | 277 | void test_disconnection() { 278 | // test removing only connected 279 | { 280 | sum = 0; 281 | sigslot::signal sig; 282 | 283 | auto sc = sig.connect(f1); 284 | sig(1); 285 | assert(sum == 1); 286 | 287 | sc.disconnect(); 288 | sig(1); 289 | assert(sum == 1); 290 | assert(!sc.valid()); 291 | } 292 | 293 | // test removing first connected 294 | { 295 | sum = 0; 296 | sigslot::signal sig; 297 | 298 | auto sc = sig.connect(f1); 299 | sig(1); 300 | assert(sum == 1); 301 | 302 | sig.connect(f2); 303 | sig(1); 304 | assert(sum == 4); 305 | 306 | sc.disconnect(); 307 | sig(1); 308 | assert(sum == 6); 309 | assert(!sc.valid()); 310 | } 311 | 312 | // test removing last connected 313 | { 314 | sum = 0; 315 | sigslot::signal sig; 316 | 317 | sig.connect(f1); 318 | sig(1); 319 | assert(sum == 1); 320 | 321 | auto sc = sig.connect(f2); 322 | sig(1); 323 | assert(sum == 4); 324 | 325 | sc.disconnect(); 326 | sig(1); 327 | assert(sum == 5); 328 | assert(!sc.valid()); 329 | } 330 | } 331 | 332 | void test_disconnection_by_callable() { 333 | // disconnect a function pointer 334 | { 335 | sum = 0; 336 | sigslot::signal sig; 337 | 338 | sig.connect(f1); 339 | sig.connect(f2); 340 | sig.connect(f2); 341 | sig(1); 342 | assert(sum == 5); 343 | auto c = sig.disconnect(&f2); 344 | assert(c == 2); 345 | sig(1); 346 | assert(sum == 6); 347 | } 348 | 349 | // disconnect a function 350 | { 351 | sum = 0; 352 | sigslot::signal sig; 353 | 354 | sig.connect(f1); 355 | sig.connect(f2); 356 | sig(1); 357 | assert(sum == 3); 358 | sig.disconnect(f1); 359 | sig(1); 360 | assert(sum == 5); 361 | } 362 | 363 | #ifdef SIGSLOT_RTTI_ENABLED 364 | // disconnect by pmf 365 | { 366 | sum = 0; 367 | sigslot::signal sig; 368 | s p; 369 | 370 | sig.connect(&s::f1, &p); 371 | sig.connect(&s::f2, &p); 372 | sig(1); 373 | assert(sum == 2); 374 | sig.disconnect(&s::f1); 375 | sig(1); 376 | assert(sum == 3); 377 | } 378 | 379 | // disconnect by function object 380 | { 381 | sum = 0; 382 | sigslot::signal sig; 383 | 384 | sig.connect(o1{}); 385 | sig.connect(o2{}); 386 | sig(1); 387 | assert(sum == 2); 388 | sig.disconnect(o1{}); 389 | sig(1); 390 | assert(sum == 3); 391 | } 392 | 393 | // disconnect by lambda 394 | { 395 | sum = 0; 396 | sigslot::signal sig; 397 | auto l1 = [&](int i) { sum += i; }; 398 | auto l2 = [&](int i) { sum += 2*i; }; 399 | sig.connect(l1); 400 | sig.connect(l2); 401 | sig(1); 402 | assert(sum == 3); 403 | sig.disconnect(l1); 404 | sig(1); 405 | assert(sum == 5); 406 | } 407 | #endif 408 | } 409 | 410 | void test_disconnection_by_object() { 411 | // disconnect by pointer 412 | { 413 | sum = 0; 414 | sigslot::signal sig; 415 | s p1, p2; 416 | 417 | sig.connect(&s::f1, &p1); 418 | sig.connect(&s::f2, &p2); 419 | sig(1); 420 | assert(sum == 2); 421 | sig.disconnect(&p1); 422 | sig(1); 423 | assert(sum == 3); 424 | } 425 | 426 | // disconnect by shared pointer 427 | { 428 | sum = 0; 429 | sigslot::signal sig; 430 | auto p1 = std::make_shared(); 431 | s p2; 432 | 433 | sig.connect(&s::f1, p1); 434 | sig.connect(&s::f2, &p2); 435 | sig(1); 436 | assert(sum == 2); 437 | sig.disconnect(p1); 438 | sig(1); 439 | assert(sum == 3); 440 | } 441 | } 442 | 443 | void test_disconnection_by_object_and_pmf() { 444 | // disconnect by pointer 445 | { 446 | sum = 0; 447 | sigslot::signal sig; 448 | s p1, p2; 449 | 450 | sig.connect(&s::f1, &p1); 451 | sig.connect(&s::f1, &p2); 452 | sig.connect(&s::f2, &p1); 453 | sig.connect(&s::f2, &p2); 454 | sig(1); 455 | assert(sum == 4); 456 | sig.disconnect(&s::f1, &p2); 457 | sig(1); 458 | assert(sum == 7); 459 | } 460 | 461 | // disconnect by shared pointer 462 | { 463 | sum = 0; 464 | sigslot::signal sig; 465 | auto p1 = std::make_shared(); 466 | auto p2 = std::make_shared(); 467 | 468 | sig.connect(&s::f1, p1); 469 | sig.connect(&s::f1, p2); 470 | sig.connect(&s::f2, p1); 471 | sig.connect(&s::f2, p2); 472 | sig(1); 473 | assert(sum == 4); 474 | sig.disconnect(&s::f1, p2); 475 | sig(1); 476 | assert(sum == 7); 477 | } 478 | 479 | // disconnect by tracker 480 | { 481 | sum = 0; 482 | sigslot::signal sig; 483 | 484 | auto t = std::make_shared(); 485 | sig.connect(f1); 486 | sig.connect(f2); 487 | sig.connect(f1, t); 488 | sig.connect(f2, t); 489 | sig(1); 490 | assert(sum == 6); 491 | sig.disconnect(f2, t); 492 | sig(1); 493 | assert(sum == 10); 494 | } 495 | } 496 | 497 | void test_scoped_connection() { 498 | sum = 0; 499 | sigslot::signal sig; 500 | 501 | { 502 | auto sc1 = sig.connect_scoped(f1); 503 | sig(1); 504 | assert(sum == 1); 505 | 506 | auto sc2 = sig.connect_scoped(f2); 507 | sig(1); 508 | assert(sum == 4); 509 | } 510 | 511 | sig(1); 512 | assert(sum == 4); 513 | 514 | sum = 0; 515 | 516 | { 517 | sigslot::scoped_connection sc1 = sig.connect(f1); 518 | sig(1); 519 | assert(sum == 1); 520 | 521 | auto sc2 = sig.connect_scoped(f2); 522 | sig(1); 523 | assert(sum == 4); 524 | } 525 | 526 | sig(1); 527 | assert(sum == 4); 528 | } 529 | 530 | void test_connection_blocking() { 531 | sum = 0; 532 | sigslot::signal sig; 533 | 534 | auto c1 = sig.connect(f1); 535 | sig.connect(f2); 536 | sig(1); 537 | assert(sum == 3); 538 | 539 | c1.block(); 540 | sig(1); 541 | assert(sum == 5); 542 | 543 | c1.unblock(); 544 | sig(1); 545 | assert(sum == 8); 546 | } 547 | 548 | void test_connection_blocker() { 549 | sum = 0; 550 | sigslot::signal sig; 551 | 552 | auto c1 = sig.connect(f1); 553 | sig.connect(f2); 554 | sig(1); 555 | assert(sum == 3); 556 | 557 | { 558 | auto cb = c1.blocker(); 559 | sig(1); 560 | assert(sum == 5); 561 | } 562 | 563 | sig(1); 564 | assert(sum == 8); 565 | } 566 | 567 | void test_signal_blocking() { 568 | sum = 0; 569 | sigslot::signal sig; 570 | 571 | sig.connect(f1); 572 | sig.connect(f2); 573 | sig(1); 574 | assert(sum == 3); 575 | 576 | sig.block(); 577 | sig(1); 578 | assert(sum == 3); 579 | 580 | sig.unblock(); 581 | sig(1); 582 | assert(sum == 6); 583 | } 584 | 585 | void test_all_disconnection() { 586 | sum = 0; 587 | sigslot::signal sig; 588 | 589 | sig.connect(f1); 590 | sig.connect(f2); 591 | sig(1); 592 | assert(sum == 3); 593 | 594 | sig.disconnect_all(); 595 | sig(1); 596 | assert(sum == 3); 597 | } 598 | 599 | void test_connection_copying_moving() { 600 | sum = 0; 601 | sigslot::signal sig; 602 | 603 | auto sc1 = sig.connect(f1); 604 | auto sc2 = sig.connect(f2); 605 | 606 | auto sc3 = sc1; 607 | auto sc4{sc2}; 608 | 609 | auto sc5 = std::move(sc3); 610 | auto sc6{std::move(sc4)}; 611 | 612 | sig(1); 613 | assert(sum == 3); 614 | 615 | sc5.block(); 616 | sig(1); 617 | assert(sum == 5); 618 | 619 | sc1.unblock(); 620 | sig(1); 621 | assert(sum == 8); 622 | 623 | sc6.disconnect(); 624 | sig(1); 625 | assert(sum == 9); 626 | } 627 | 628 | void test_scoped_connection_moving() { 629 | sum = 0; 630 | sigslot::signal sig; 631 | 632 | { 633 | auto sc1 = sig.connect_scoped(f1); 634 | sig(1); 635 | assert(sum == 1); 636 | 637 | auto sc2 = sig.connect_scoped(f2); 638 | sig(1); 639 | assert(sum == 4); 640 | 641 | auto sc3 = std::move(sc1); 642 | sig(1); 643 | assert(sum == 7); 644 | 645 | auto sc4{std::move(sc2)}; 646 | sig(1); 647 | assert(sum == 10); 648 | } 649 | 650 | sig(1); 651 | assert(sum == 10); 652 | } 653 | 654 | void test_signal_moving() { 655 | sum = 0; 656 | sigslot::signal sig; 657 | 658 | sig.connect(f1); 659 | sig.connect(f2); 660 | 661 | sig(1); 662 | assert(sum == 3); 663 | 664 | auto sig2 = std::move(sig); 665 | sig2(1); 666 | assert(sum == 6); 667 | 668 | auto sig3 = std::move(sig2); 669 | sig3(1); 670 | assert(sum == 9); 671 | } 672 | 673 | template 674 | struct object { 675 | object(); 676 | object(T i) : v{i} {} 677 | 678 | const T & val() const { return v; } 679 | T & val() { return v; } 680 | void set_val(const T &i) { 681 | if (i != v) { 682 | v = i; 683 | s(i); 684 | } 685 | } 686 | 687 | sigslot::signal & sig() { return s; } 688 | 689 | private: 690 | T v; 691 | sigslot::signal s; 692 | }; 693 | 694 | void test_loop() { 695 | object i1(0); 696 | object i2(3); 697 | 698 | i1.sig().connect(&object::set_val, &i2); 699 | i2.sig().connect(&object::set_val, &i1); 700 | 701 | i1.set_val(1); 702 | 703 | assert(i1.val() == 1); 704 | assert(i2.val() == 1); 705 | } 706 | 707 | int main() { 708 | test_free_connection(); 709 | test_static_connection(); 710 | test_pmf_connection(); 711 | test_const_pmf_connection(); 712 | test_function_object_connection(); 713 | test_overloaded_function_object_connection(); 714 | test_lambda_connection(); 715 | test_generic_lambda_connection(); 716 | test_lvalue_emission(); 717 | test_compatible_args(); 718 | test_compatible_args_chaining(); 719 | test_mutation(); 720 | test_disconnection(); 721 | test_disconnection_by_callable(); 722 | test_disconnection_by_object(); 723 | test_disconnection_by_object_and_pmf(); 724 | test_scoped_connection(); 725 | test_connection_blocker(); 726 | test_connection_blocking(); 727 | test_signal_blocking(); 728 | test_all_disconnection(); 729 | test_connection_copying_moving(); 730 | test_scoped_connection_moving(); 731 | test_signal_moving(); 732 | test_loop(); 733 | test_slot_count(); 734 | return 0; 735 | } 736 | -------------------------------------------------------------------------------- /test/slots-groups.cpp: -------------------------------------------------------------------------------- 1 | #include "test-common.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using res_container = std::vector; 9 | 10 | static constexpr size_t num_groups = 100; 11 | static constexpr size_t num_slots = 1000; 12 | 13 | static auto pusher(int pos) { 14 | return [pos=std::move(pos)] (res_container &c) { 15 | c.push_back(pos); 16 | }; 17 | } 18 | 19 | static auto adder(int v) { 20 | return [v=std::move(v)] (int &s) { 21 | s += v; 22 | }; 23 | } 24 | 25 | static void test_random_groups() { 26 | res_container results; 27 | sigslot::signal sig; 28 | 29 | std::mt19937_64 gen{std::random_device()()}; 30 | 31 | // create N groups with random ids 32 | std::uniform_int_distribution dist(std::numeric_limits::lowest()); 33 | std::array gids; 34 | std::generate_n(gids.begin(), num_groups, [&] { return dist(gen); }); 35 | 36 | // create 37 | std::uniform_int_distribution slots_dist { 0, num_groups-1 }; 38 | 39 | for (size_t i = 0; i < num_slots; ++i) { 40 | auto gid = gids[slots_dist(gen)]; 41 | sig.connect(pusher(gid), gid); 42 | } 43 | 44 | // signal 45 | sig(results); 46 | 47 | // check that the resulting container is sorted 48 | assert(std::is_sorted(results.begin(), results.end())); 49 | } 50 | 51 | static void test_disconnect_group() { 52 | int sum = 0; 53 | sigslot::signal sig; 54 | sig.connect(adder(3), 3); 55 | sig.connect(adder(1), 1); 56 | sig.connect(adder(2), 2); 57 | 58 | sig(sum); 59 | assert(sum == 6); 60 | 61 | sig.disconnect(2); 62 | sig(sum); 63 | assert(sum == 10); 64 | } 65 | 66 | int main() { 67 | test_random_groups(); 68 | test_disconnect_group(); 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /test/test-common.h: -------------------------------------------------------------------------------- 1 | #ifdef NDEBUG 2 | #undef NDEBUG 3 | #endif 4 | --------------------------------------------------------------------------------