├── tests
├── libfastsignals_unit_tests
│ ├── main.cpp
│ ├── CMakeLists.txt
│ ├── libfastsignals_unit_tests.vcxproj.filters
│ ├── bind_weak_tests.cpp
│ ├── libfastsignals_unit_tests.vcxproj
│ ├── Function_tests.cpp
│ └── signal_tests.cpp
└── libfastsignals_stress_tests
│ ├── main.cpp
│ ├── CMakeLists.txt
│ ├── libfastsignals_stress_tests.vcxproj.filters
│ ├── signal_stress_tests.cpp
│ └── libfastsignals_stress_tests.vcxproj
├── libfastsignals
├── CMakeLists.txt
├── include
│ ├── type_traits.h
│ ├── combiners.h
│ ├── spin_mutex.h
│ ├── msvc_autolink.h
│ ├── signal_impl.h
│ ├── function.h
│ ├── bind_weak.h
│ ├── connection.h
│ ├── signal.h
│ └── function_detail.h
├── libfastsignals.vcxproj.filters
├── src
│ ├── signal_impl.cpp
│ ├── function_detail.cpp
│ └── connection.cpp
└── libfastsignals.vcxproj
├── .travis.yml
├── README.md
├── CMakeLists.txt
├── LICENSE
├── libfastsignals_build_options.props
├── docs
├── simple-examples.md
├── why-fastsignals.md
├── bind_weak.md
└── migration-from-boost-signals2.md
├── cmake
└── functions.cmake
├── FastSignals.sln
├── .clang-format
└── .gitignore
/tests/libfastsignals_unit_tests/main.cpp:
--------------------------------------------------------------------------------
1 | #define CATCH_CONFIG_MAIN
2 | #include "catch2/catch.hpp"
3 |
--------------------------------------------------------------------------------
/tests/libfastsignals_stress_tests/main.cpp:
--------------------------------------------------------------------------------
1 | #define CATCH_CONFIG_MAIN
2 | #include "catch2/catch.hpp"
3 |
--------------------------------------------------------------------------------
/tests/libfastsignals_stress_tests/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | custom_add_test_from_dir(libfastsignals_stress_tests libfastsignals)
3 |
--------------------------------------------------------------------------------
/tests/libfastsignals_unit_tests/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | custom_add_test_from_dir(libfastsignals_unit_tests libfastsignals)
3 | custom_enable_cxx17(libfastsignals_unit_tests)
4 | target_include_directories(libfastsignals_unit_tests PRIVATE "${CMAKE_SOURCE_DIR}/tests")
5 |
--------------------------------------------------------------------------------
/libfastsignals/CMakeLists.txt:
--------------------------------------------------------------------------------
1 |
2 | file(GLOB LIBFASTSIGNALS_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/*.h")
3 | add_library(libfastsignals ${LIBFASTSIGNALS_SRC})
4 | custom_enable_cxx17(libfastsignals)
5 | target_include_directories(libfastsignals INTERFACE "${CMAKE_SOURCE_DIR}")
6 |
--------------------------------------------------------------------------------
/tests/libfastsignals_stress_tests/libfastsignals_stress_tests.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/tests/libfastsignals_unit_tests/libfastsignals_unit_tests.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/libfastsignals/include/type_traits.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | namespace is::signals
4 | {
5 | namespace detail
6 | {
7 |
8 | template
9 | struct signal_arg
10 | {
11 | using type = const T&;
12 | };
13 |
14 | template
15 | struct signal_arg
16 | {
17 | using type = U&;
18 | };
19 | } // namespace detail
20 |
21 | template
22 | using signal_arg_t = typename detail::signal_arg::type;
23 |
24 | } // namespace is::signals
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | sudo: required
3 | language: cpp
4 |
5 | os:
6 | - linux
7 |
8 | compiler:
9 | - gcc
10 | # TODO: - clang
11 |
12 | env:
13 | - TARGET_CPU=amd64 BUILD_CONFIGURATION=Release CI_NAME=TRAVIS
14 | # TODO: - TARGET_CPU=x86 BUILD_CONFIGURATION=Release
15 |
16 | before_install:
17 | # build environment setup script
18 | - source build/travis-install-env-$TRAVIS_OS_NAME.sh
19 |
20 | script:
21 | # Build project
22 | - mkdir -p build && cd build
23 | - cmake .. -DCMAKE_BUILD_TYPE=$BUILD_CONFIGURATION -DTARGET_CPU=$TARGET_CPU -DBUILD_TESTING=ON
24 | - cmake --build .
25 |
26 | # Run tests
27 | - tests/libfastsignals_unit_tests/libfastsignals_unit_tests
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FastSignals
2 |
3 | Yet another C++ signals and slots library
4 |
5 | * Works as drop-in replacement for Boost.Signals2 with the same API
6 | * Has better performance and more compact binary code
7 | * Thread-safe in most operations, including concurrent connects/disconnects/emits
8 | * Implemented with compact, pure C++17 code
9 |
10 | [](https://travis-ci.org/ispringteam/FastSignals)
11 | [](https://opensource.org/licenses/MIT)
12 |
13 | ## Documentation
14 |
15 | * [Why FastSignals?](docs/why-fastsignals.md)
16 | * [Simple Examples](docs/simple-examples.md)
17 | * [Migration from Boost.Signals2](docs/migration-from-boost-signals2.md)
18 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR)
2 |
3 | project(FastSignals)
4 |
5 | # Use `-DBUILD_TESTING=OFF` to disable testing/benchmarking.
6 | enable_testing()
7 |
8 | include(cmake/functions.cmake)
9 |
10 | add_subdirectory(libfastsignals)
11 |
12 | if(BUILD_TESTING)
13 | # add_subdirectory(tests/benchmark)
14 | # add_subdirectory(tests/libfastsignals_bench)
15 | #add_subdirectory(tests/libfastsignals_stress_tests)
16 | add_subdirectory(tests/libfastsignals_unit_tests)
17 | endif()
18 |
19 | # Trace variables, both environment and internal.
20 | execute_process(COMMAND "${CMAKE_COMMAND}" "-E" "environment")
21 | get_cmake_property(_variableNames VARIABLES)
22 | list (SORT _variableNames)
23 | foreach (_variableName ${_variableNames})
24 | message(STATUS "${_variableName}=${${_variableName}}")
25 | endforeach()
26 |
--------------------------------------------------------------------------------
/libfastsignals/include/combiners.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | namespace is::signals
6 | {
7 |
8 | /**
9 | * This results combiner reduces results collection into last value of this collection.
10 | * In other words, it keeps only result of the last slot call.
11 | */
12 | template
13 | class optional_last_value
14 | {
15 | public:
16 | using result_type = std::optional;
17 |
18 | template
19 | void operator()(TRef&& value)
20 | {
21 | m_result = std::forward(value);
22 | }
23 |
24 | result_type get_value() const
25 | {
26 | return m_result;
27 | }
28 |
29 | private:
30 | result_type m_result = {};
31 | };
32 |
33 | template <>
34 | class optional_last_value
35 | {
36 | public:
37 | using result_type = void;
38 | };
39 |
40 | } // namespace is::signals
41 |
--------------------------------------------------------------------------------
/libfastsignals/include/spin_mutex.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 | namespace is::signals::detail
5 | {
6 |
7 | class spin_mutex
8 | {
9 | public:
10 | spin_mutex() = default;
11 | spin_mutex(const spin_mutex&) = delete;
12 | spin_mutex& operator=(const spin_mutex&) = delete;
13 | spin_mutex(spin_mutex&&) = delete;
14 | spin_mutex& operator=(spin_mutex&&) = delete;
15 |
16 | inline bool try_lock() noexcept
17 | {
18 | return !m_busy.test_and_set(std::memory_order_acquire);
19 | }
20 |
21 | inline void lock() noexcept
22 | {
23 | while (!try_lock())
24 | {
25 | /* do nothing */;
26 | }
27 | }
28 |
29 | inline void unlock() noexcept
30 | {
31 | m_busy.clear(std::memory_order_release);
32 | }
33 |
34 | private:
35 | std::atomic_flag m_busy = ATOMIC_FLAG_INIT;
36 | };
37 |
38 | } // namespace is::signals::detail
39 |
--------------------------------------------------------------------------------
/libfastsignals/include/msvc_autolink.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #if defined(_MSC_VER)
4 |
5 | # if defined(__clang__)
6 | # if defined(_DEBUG) && defined(_WIN64)
7 | # pragma comment(lib, "libfastsignalsd-llvm-x64.lib")
8 | # elif defined(_DEBUG)
9 | # pragma comment(lib, "libfastsignalsd-llvm-x32.lib")
10 | # elif defined(_WIN64)
11 | # pragma comment(lib, "libfastsignals-llvm-x64.lib")
12 | # else
13 | # pragma comment(lib, "libfastsignals-llvm-x32.lib")
14 | # endif
15 | # elif _MSC_VER <= 1900
16 | # error this library needs Visual Studio 2017 and higher
17 | # elif _MSC_VER < 2000
18 | # if defined(_DEBUG) && defined(_WIN64)
19 | # pragma comment(lib, "libfastsignalsd-v141-x64.lib")
20 | # elif defined(_DEBUG)
21 | # pragma comment(lib, "libfastsignalsd-v141-x32.lib")
22 | # elif defined(_WIN64)
23 | # pragma comment(lib, "libfastsignals-v141-x64.lib")
24 | # else
25 | # pragma comment(lib, "libfastsignals-v141-x32.lib")
26 | # endif
27 | # else
28 | # error unknown Visual Studio version, auto-linking setup failed
29 | # endif
30 |
31 | #endif
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 iSpring Solutions Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/libfastsignals_build_options.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | d
7 |
8 |
9 | -x32
10 | -x64
11 |
12 |
13 |
14 |
15 | MultiThreaded
16 | false
17 |
18 |
19 | Default
20 |
21 |
22 |
23 |
24 | MultiThreadedDebug
25 | Level4
26 | true
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/docs/simple-examples.md:
--------------------------------------------------------------------------------
1 | # Simple Examples
2 |
3 | >If you are not familar with Boost.Signals2, please read [Boost.Signals2: Connections](https://theboostcpplibraries.com/boost.signals2-connections)
4 |
5 | ## Example with signal<> and connection
6 |
7 | ```cpp
8 | // Creates signal and connects 1 slot, calls 2 times, disconnects, calls again.
9 | // Outputs:
10 | // 13
11 | // 17
12 | #include "libfastsignals/signal.h"
13 |
14 | using namespace is::signals;
15 |
16 | int main()
17 | {
18 | signal valueChanged;
19 | connection conn;
20 | conn = valueChanged.connect([](int value) {
21 | cout << value << endl;
22 | });
23 | valueChanged(13);
24 | valueChanged(17);
25 | conn.disconnect();
26 | valueChanged(42);
27 | }
28 | ```
29 |
30 | ## Example with scoped_connection
31 |
32 | ```cpp
33 | // Creates signal and connects 1 slot, calls 2 times, calls again after scoped_connection destroyed.
34 | // - note: scoped_connection closes connection in destructor
35 | // Outputs:
36 | // 13
37 | // 17
38 | #include "libfastsignals/signal.h"
39 |
40 | using namespace is::signals;
41 |
42 | int main()
43 | {
44 | signal valueChanged;
45 | {
46 | scoped_connection conn;
47 | conn = valueChanged.connect([](int value) {
48 | cout << value << endl;
49 | });
50 | valueChanged(13);
51 | valueChanged(17);
52 | }
53 | valueChanged(42);
54 | }
55 | ```
56 |
--------------------------------------------------------------------------------
/libfastsignals/include/signal_impl.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "function_detail.h"
4 | #include "spin_mutex.h"
5 | #include
6 | #include
7 |
8 | namespace is::signals::detail
9 | {
10 |
11 | class signal_impl
12 | {
13 | public:
14 | uint64_t add(packed_function fn);
15 |
16 | void remove(uint64_t id) noexcept;
17 |
18 | void remove_all() noexcept;
19 |
20 | size_t count() const noexcept;
21 |
22 | template
23 | Result invoke(Args... args) const
24 | {
25 | packed_function slot;
26 | size_t slotIndex = 0;
27 | uint64_t slotId = 1;
28 |
29 | if constexpr (std::is_same_v)
30 | {
31 | while (get_next_slot(slot, slotIndex, slotId))
32 | {
33 | slot.get()(std::forward(args)...);
34 | }
35 | }
36 | else
37 | {
38 | Combiner combiner;
39 | while (get_next_slot(slot, slotIndex, slotId))
40 | {
41 | combiner(slot.get()(std::forward(args)...));
42 | }
43 | return combiner.get_value();
44 | }
45 | }
46 |
47 | private:
48 | bool get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const;
49 |
50 | mutable spin_mutex m_mutex;
51 | std::vector m_functions;
52 | std::vector m_ids;
53 | uint64_t m_nextId = 1;
54 | };
55 |
56 | using signal_impl_ptr = std::shared_ptr;
57 | using signal_impl_weak_ptr = std::weak_ptr;
58 |
59 | } // namespace is::signals::detail
60 |
--------------------------------------------------------------------------------
/docs/why-fastsignals.md:
--------------------------------------------------------------------------------
1 | # Why FastSignals?
2 |
3 | FastSignals is a C++17 signals/slots implementation which API is compatible with Boost.Signals2.
4 |
5 | FastSignals pros:
6 |
7 | * Faster than Boost.Signals2
8 | * Has more compact binary code
9 | * Has the same API as Boost.Signals2
10 |
11 | FastSignals cons:
12 |
13 | * Supports only C++17 compatible compilers: Visual Studio 2017, modern Clang, modern GCC
14 | * Lacks a few rarely used features presented in Boost.Signals2
15 | * No access to connection from slot with `signal::connect_extended` method
16 | * No connected object tracking with `slot::track` method
17 | * Use [bind_weak](bind_weak.md) instead
18 | * No temporary signal blocking with `shared_connection_block` class
19 | * Cannot disconnect equivalent slots since no `disconnect(slot)` function overload
20 | * Any other API difference is a bug - please report it!
21 |
22 | See also [Migration from Boost.Signals2](migration-from-boost-signals2.md).
23 |
24 | ## Benchmark results
25 |
26 | Directory `tests/libfastsignals_bench` contains simple benchmark with compares two signal/slot implementations:
27 |
28 | * Boost.Signals2
29 | * libfastsignals
30 |
31 | Benchmark compairs performance when signal emitted frequently with 0, 1 and 8 active connections. In these cases libfastsignals is 3-6 times faster.
32 |
33 | ```
34 | *** Results:
35 | measure emit_boost emit_fastsignals
36 | emit_boost/0 1.00 3.00
37 | emit_boost/1 1.00 5.76
38 | emit_boost/8 1.00 3.70
39 | ***
40 | ```
41 |
--------------------------------------------------------------------------------
/libfastsignals/include/function.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "function_detail.h"
4 |
5 | namespace is::signals
6 | {
7 | // Derive your class from not_directly_callable to prevent function from wrapping it using its template constructor
8 | // Useful if your class provides custom operator for casting to function
9 | struct not_directly_callable
10 | {
11 | };
12 |
13 | template
14 | using enable_if_callable_t = typename std::enable_if_t<
15 | !std::is_same_v, Function> && !std::is_base_of_v> && std::is_same_v, Return>>;
16 |
17 | template
18 | class function;
19 |
20 | // Compact function class - causes minimal code bloat when compiled.
21 | // Replaces std::function in this library.
22 | template
23 | class function
24 | {
25 | public:
26 | function() = default;
27 |
28 | function(const function& other) = default;
29 | function(function&& other) noexcept = default;
30 | function& operator=(const function& other) = default;
31 | function& operator=(function&& other) noexcept = default;
32 |
33 | template , Return, Arguments...>>
34 | function(Fn&& function) noexcept(detail::is_noexcept_packed_function_init)
35 | {
36 | m_packed.init(std::forward(function));
37 | }
38 |
39 | Return operator()(Arguments&&... args) const
40 | {
41 | auto& proxy = m_packed.get();
42 | return proxy(std::forward(args)...);
43 | }
44 |
45 | detail::packed_function release() noexcept
46 | {
47 | return std::move(m_packed);
48 | }
49 |
50 | private:
51 | detail::packed_function m_packed;
52 | };
53 |
54 | } // namespace is::signals
55 |
--------------------------------------------------------------------------------
/cmake/functions.cmake:
--------------------------------------------------------------------------------
1 | # В текущей версии CMake не может включить режим C++17 в некоторых компиляторах.
2 | # Функция использует обходной манёвр.
3 | function(custom_enable_cxx17 TARGET)
4 | # Включаем C++17 везде, где CMake может.
5 | target_compile_features(${TARGET} PUBLIC cxx_std_17)
6 | # Включаем режим C++latest в Visual Studio
7 | if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
8 | set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "/std:c++latest")
9 | # Включаем компоновку с libc++, libc++experimental и pthread для Clang
10 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
11 | set_target_properties(${TARGET} PROPERTIES COMPILE_FLAGS "-stdlib=libc++ -pthread")
12 | target_link_libraries(${TARGET} c++experimental pthread)
13 | endif()
14 | endfunction(custom_enable_cxx17)
15 |
16 | # Функция добавляет цель-библиотеку.
17 | function(custom_add_library_from_dir TARGET)
18 | # Собираем файлы с текущего каталога
19 | file(GLOB TARGET_SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
20 | add_library(${TARGET} ${TARGET_SRC})
21 | endfunction()
22 |
23 | # Функция добавляет цель исполняемого файла.
24 | function(custom_add_executable_from_dir TARGET)
25 | # Собираем файлы с текущего каталога
26 | file(GLOB TARGET_SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/*.h")
27 | add_executable(${TARGET} ${TARGET_SRC})
28 | endfunction()
29 |
30 | # Функция добавляет цель исполняемого файла, содержащего тесты библиотеки.
31 | function(custom_add_test_from_dir TARGET LIBRARY)
32 | custom_add_executable_from_dir(${TARGET})
33 | # Добавляем путь к заголовку фреймворка Catch
34 | target_include_directories(${TARGET} PRIVATE "${CMAKE_SOURCE_DIR}/libs/catch")
35 | # Добавляем компоновку с проверяемой библиотекой
36 | target_link_libraries(${TARGET} ${LIBRARY})
37 | # Регистрируем исполняемый файл в CMake как набор тестов.
38 | add_test(${TARGET} ${TARGET})
39 | endfunction()
40 |
--------------------------------------------------------------------------------
/libfastsignals/libfastsignals.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | include
6 |
7 |
8 | include
9 |
10 |
11 | include
12 |
13 |
14 | include
15 |
16 |
17 | include
18 |
19 |
20 | include
21 |
22 |
23 | include
24 |
25 |
26 | include
27 |
28 |
29 | include
30 |
31 |
32 | include
33 |
34 |
35 |
36 |
37 | {474d3307-8dbe-47d6-a12f-35f944912d9d}
38 |
39 |
40 | {ac074187-2f8f-44b9-a170-24568deb06e6}
41 |
42 |
43 |
44 |
45 | src
46 |
47 |
48 | src
49 |
50 |
51 | src
52 |
53 |
54 |
--------------------------------------------------------------------------------
/libfastsignals/src/signal_impl.cpp:
--------------------------------------------------------------------------------
1 | #include "../include/signal_impl.h"
2 | #include
3 | #include
4 |
5 | namespace is::signals::detail
6 | {
7 |
8 | uint64_t signal_impl::add(packed_function fn)
9 | {
10 | std::lock_guard lock(m_mutex);
11 |
12 | m_functions.emplace_back(std::move(fn));
13 |
14 | try
15 | {
16 | m_ids.emplace_back(m_nextId);
17 | }
18 | catch (const std::bad_alloc& /*e*/)
19 | {
20 | // Remove function since we failed to add its id
21 | m_functions.pop_back();
22 | throw;
23 | }
24 |
25 | return m_nextId++;
26 | }
27 |
28 | void signal_impl::remove(uint64_t id) noexcept
29 | {
30 | std::lock_guard lock(m_mutex);
31 |
32 | // We use binary search because ids array is always sorted.
33 | auto it = std::lower_bound(m_ids.begin(), m_ids.end(), id);
34 | if (it != m_ids.end() && *it == id)
35 | {
36 | size_t i = std::distance(m_ids.begin(), it);
37 | m_ids.erase(m_ids.begin() + i);
38 | m_functions.erase(m_functions.begin() + i);
39 | }
40 | }
41 |
42 | void signal_impl::remove_all() noexcept
43 | {
44 | std::lock_guard lock(m_mutex);
45 | m_functions.clear();
46 | m_ids.clear();
47 | }
48 |
49 | bool signal_impl::get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const
50 | {
51 | // Slots always arranged by ID, so we can use a simple algorithm which avoids races:
52 | // - on each step find first slot with ID >= slotId
53 | // - after each call increment slotId
54 |
55 | std::lock_guard lock(m_mutex);
56 |
57 | // Avoid binary search if next slot wasn't moved between mutex locks.
58 | if (expectedIndex >= m_ids.size() || m_ids[expectedIndex] != nextId)
59 | {
60 | auto it = (nextId < m_nextId)
61 | ? std::lower_bound(m_ids.cbegin(), m_ids.cend(), nextId)
62 | : m_ids.end();
63 | if (it == m_ids.end())
64 | {
65 | return false;
66 | }
67 | expectedIndex = std::distance(m_ids.cbegin(), it);
68 | }
69 |
70 | slot.reset();
71 | slot = m_functions[expectedIndex];
72 | nextId = (expectedIndex + 1 < m_ids.size()) ? m_ids[expectedIndex + 1] : m_ids[expectedIndex] + 1;
73 | ++expectedIndex;
74 | return true;
75 | }
76 |
77 | size_t signal_impl::count() const noexcept
78 | {
79 | std::lock_guard lock(m_mutex);
80 |
81 | return m_functions.size();
82 | }
83 |
84 | } // namespace is::signals::detail
85 |
--------------------------------------------------------------------------------
/libfastsignals/src/function_detail.cpp:
--------------------------------------------------------------------------------
1 | #include "../include/function_detail.h"
2 | #include
3 | #include
4 |
5 | namespace is::signals::detail
6 | {
7 |
8 | packed_function::packed_function(packed_function&& other) noexcept
9 | : m_proxy(move_proxy_from(std::move(other)))
10 | {
11 | }
12 |
13 | packed_function::packed_function(const packed_function& other)
14 | : m_proxy(clone_proxy_from(other))
15 | {
16 | }
17 |
18 | packed_function& packed_function::operator=(packed_function&& other) noexcept
19 | {
20 | assert(this != &other);
21 | reset();
22 | m_proxy = move_proxy_from(std::move(other));
23 | return *this;
24 | }
25 |
26 | base_function_proxy* packed_function::move_proxy_from(packed_function&& other) noexcept
27 | {
28 | auto proxy = other.m_proxy ? other.m_proxy->move(&m_buffer) : nullptr;
29 | other.m_proxy = nullptr;
30 | return proxy;
31 | }
32 |
33 | base_function_proxy* packed_function::clone_proxy_from(const packed_function& other)
34 | {
35 | return other.m_proxy ? other.m_proxy->clone(&m_buffer) : nullptr;
36 | }
37 |
38 | packed_function& packed_function::operator=(const packed_function& other)
39 | {
40 | if (this != &other)
41 | {
42 | if (other.is_buffer_allocated() && is_buffer_allocated())
43 | {
44 | // "This" and "other" are using SBO. Safe assignment must use copy+move
45 | *this = packed_function(other);
46 | }
47 | else
48 | {
49 | // Buffer is used either by "this" or by "other" or not used at all.
50 | // If this uses buffer then other's proxy is null or allocated on heap, so clone won't overwrite buffer
51 | // If this uses heap or null then other's proxy can safely use buffer because reset() won't access buffer
52 | auto newProxy = clone_proxy_from(other);
53 | reset();
54 | m_proxy = newProxy;
55 | }
56 | }
57 | return *this;
58 | }
59 |
60 | packed_function::~packed_function() noexcept
61 | {
62 | reset();
63 | }
64 |
65 | void packed_function::reset() noexcept
66 | {
67 | if (m_proxy != nullptr)
68 | {
69 | if (is_buffer_allocated())
70 | {
71 | m_proxy->~base_function_proxy();
72 | }
73 | else
74 | {
75 | delete m_proxy;
76 | }
77 | m_proxy = nullptr;
78 | }
79 | }
80 |
81 | base_function_proxy& packed_function::unwrap() const
82 | {
83 | if (m_proxy == nullptr)
84 | {
85 | throw std::bad_function_call();
86 | }
87 | return *m_proxy;
88 | }
89 |
90 | bool packed_function::is_buffer_allocated() const noexcept
91 | {
92 | return std::less_equal()(&m_buffer[0], m_proxy)
93 | && std::less()(m_proxy, &m_buffer[1]);
94 | }
95 |
96 | } // namespace is::signals::detail
97 |
--------------------------------------------------------------------------------
/libfastsignals/include/bind_weak.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | namespace is::signals
4 | {
5 | namespace detail
6 | {
7 | template
8 | struct weak_binder
9 | {
10 | using ConstMethodType = ReturnType (ClassType::*)(Args... args) const;
11 | using NonConstMethodType = ReturnType (ClassType::*)(Args... args);
12 | using MethodType = std::conditional_t;
13 | using WeakPtrType = std::weak_ptr;
14 |
15 | weak_binder(MethodType pMethod, WeakPtrType&& pObject)
16 | : m_pMethod(pMethod)
17 | , m_pObject(pObject)
18 | {
19 | }
20 |
21 | ReturnType operator()(Args... args) const
22 | {
23 | if (auto pThis = m_pObject.lock())
24 | {
25 | return (pThis.get()->*m_pMethod)(std::forward(args)...);
26 | }
27 | return ReturnType();
28 | }
29 |
30 | MethodType m_pMethod;
31 | WeakPtrType m_pObject;
32 | };
33 | } // namespace detail
34 |
35 | /// Weak this binding of non-const methods.
36 | template
37 | decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args), std::shared_ptr const& pThis, Args... args)
38 | {
39 | using weak_binder_alias = detail::weak_binder;
40 |
41 | weak_binder_alias invoker(memberFn, std::weak_ptr(pThis));
42 | return std::bind(invoker, args...);
43 | }
44 |
45 | /// Weak this binding of const methods.
46 | template
47 | decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args) const, std::shared_ptr const& pThis, Args... args)
48 | {
49 | using weak_binder_alias = detail::weak_binder;
50 |
51 | weak_binder_alias invoker(memberFn, std::weak_ptr(pThis));
52 | return std::bind(invoker, args...);
53 | }
54 |
55 | /// Weak this binding of non-const methods.
56 | template
57 | decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args), std::weak_ptr pThis, Args... args)
58 | {
59 | using weak_binder_alias = detail::weak_binder;
60 |
61 | weak_binder_alias invoker(memberFn, std::move(pThis));
62 | return std::bind(invoker, args...);
63 | }
64 |
65 | /// Weak this binding of const methods.
66 | template
67 | decltype(auto) bind_weak(ReturnType (ClassType::*memberFn)(Params... args) const, std::weak_ptr pThis, Args... args)
68 | {
69 | using weak_binder_alias = detail::weak_binder;
70 |
71 | weak_binder_alias invoker(memberFn, std::move(pThis));
72 | return std::bind(invoker, args...);
73 | }
74 |
75 | } // namespace is::signals
76 |
--------------------------------------------------------------------------------
/docs/bind_weak.md:
--------------------------------------------------------------------------------
1 | # Function bind_weak
2 |
3 | ## Usage
4 |
5 | * Use `is::signals::bind_weak` instead of `std::bind` to ensure that nothing happens if method called when binded object already destroyed
6 | * Pass pointer to T class method as first argument, `shared_ptr` or `weak_ptr` as second argument
7 | * Example: `bind_weak(&Document::save(), document, std::placeholders::_1)`, where `document` is a `weak_ptr` or `shared_ptr`
8 |
9 | ## Weak this idiom
10 |
11 | The `is::signals::bind_weak(...)` function implements "weak this" idiom. This idiom helps to avoid dangling pointers and memory access wiolations in asynchronous and/or multithreaded programs.
12 |
13 | In the following example, we use weak this idiom to avoid using dangling pointer wehn calling `print()` method of the `Enityt`:
14 |
15 | ```cpp
16 | struct Entity : std::enable_shared_from_this
17 | {
18 | int value = 42;
19 |
20 | void print()
21 | {
22 | std::cout << "print called, num = " << value << std::endl;
23 | }
24 |
25 | std::function print_later()
26 | {
27 | // ! weak this idiom here !
28 | auto weak_this = weak_from_this();
29 | return [weak_this] {
30 | if (auto shared_this = weak_this.lock())
31 | {
32 | shared_this->print();
33 | }
34 | };
35 | }
36 | };
37 |
38 | int main()
39 | {
40 | auto entity = std::make_shared();
41 | auto print = entity->print_later();
42 |
43 | // Prints OK.
44 | print();
45 |
46 | // Prints nothing - last shared_ptr to the Entity destroyed, so `weak_this.lock()` will return nullptr.
47 | entity = nullptr;
48 | print();
49 | }
50 | ```
51 |
52 | ## Using bind_weak to avoid signal receiver lifetime issues
53 |
54 | In the following example, `Entity::print()` method connected to the signal. Signal emmited once before and once after the `Entity` instance destroyed. However, no memory access violation happens: once `Entity` destoryed, no slot will be called because `bind_weak` doesn't call binded method if it cannot lock `std::weak_ptr` to binded object. The second `event()` expression just does nothing.
55 |
56 | ```cpp
57 | #include "fastsignals/signal.h"
58 | #include "fastsignals/bind_weak.h"
59 | #include
60 |
61 | using VoidSignal = is::signals::signal;
62 | using VoidSlot = VoidSignal::slot_type;
63 |
64 | struct Entity : std::enable_shared_from_this
65 | {
66 | int value = 42;
67 |
68 | VoidSlot get_print_slot()
69 | {
70 | // Here is::signals::bind_weak() used instead of std::bind.
71 | return is::signals::bind_weak(&Entity::print, weak_from_this());
72 | }
73 |
74 | void print()
75 | {
76 | std::cout << "print called, num = " << value << std::endl;
77 | }
78 | };
79 |
80 | int main()
81 | {
82 | VoidSignal event;
83 | auto entity = std::make_shared();
84 | event.connect(entity->get_print_slot());
85 |
86 | // Here slot called - it prints `slot called, num = 42`
87 | event();
88 | entity = nullptr;
89 |
90 | // Here nothing happens - no exception, no slot call.
91 | event();
92 | }
93 |
94 | ```
95 |
--------------------------------------------------------------------------------
/tests/libfastsignals_stress_tests/signal_stress_tests.cpp:
--------------------------------------------------------------------------------
1 | #include "catch2/catch.hpp"
2 | #include "libfastsignals/include/signal.h"
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | using namespace is::signals;
9 |
10 | namespace
11 | {
12 | using string_signal = signal;
13 | using string_slot = string_signal::slot_type;
14 | using void_signal = signal;
15 | using void_slot = void_signal::slot_type;
16 |
17 | class named_entity
18 | {
19 | public:
20 | std::string name() const
21 | {
22 | std::lock_guard lock(m_nameMutex);
23 | return m_name;
24 | }
25 |
26 | void fire_changed(std::string value)
27 | {
28 | bool fire = false;
29 | {
30 | std::lock_guard lock(m_nameMutex);
31 | if (m_name != value)
32 | {
33 | m_name = std::move(value);
34 | fire = true;
35 | }
36 | }
37 | if (fire)
38 | {
39 | m_nameChanged(value);
40 | }
41 | }
42 |
43 | connection on_name_changed(string_slot slot)
44 | {
45 | return m_nameChanged.connect(std::move(slot));
46 | }
47 |
48 | private:
49 | mutable std::mutex m_nameMutex;
50 | std::string m_name;
51 | signal m_nameChanged;
52 | };
53 |
54 | unsigned get_next_seed()
55 | {
56 | static std::minstd_rand seedEngine(777);
57 | return seedEngine();
58 | }
59 |
60 | size_t get_random_index(size_t size)
61 | {
62 | thread_local std::minstd_rand disconnectRandomEngine{ get_next_seed() };
63 | std::uniform_int_distribution disconnectIndexDistribution{ 0, size - 1 };
64 |
65 | return disconnectIndexDistribution(disconnectRandomEngine);
66 | }
67 | } // namespace
68 |
69 | TEST_CASE("Can work in a few threads", "[signal]")
70 | {
71 | constexpr unsigned fireThreadCount = 8;
72 | constexpr unsigned signalsCount = 7;
73 | constexpr unsigned fireCountPerThread = 100'000;
74 | constexpr unsigned connectCallsCount = 80'000;
75 | constexpr unsigned totalRunCount = 10;
76 |
77 | for (unsigned i = 0; i < totalRunCount; ++i)
78 | {
79 | std::array signals;
80 |
81 | std::mutex connectionsMutex;
82 | std::vector connections;
83 | connections.reserve(connectCallsCount);
84 |
85 | std::vector threads;
86 |
87 | auto slot = [&] {
88 | std::lock_guard lock(connectionsMutex);
89 | if (!connections.empty())
90 | {
91 | const size_t index = get_random_index(connections.size());
92 | connections.at(index).disconnect();
93 | }
94 | };
95 |
96 | threads.emplace_back([&] {
97 | for (unsigned cci = 0; cci < connectCallsCount; ++cci)
98 | {
99 | const size_t index = get_random_index(signalsCount);
100 | connection conn = signals.at(index).connect(slot);
101 | {
102 | std::lock_guard lock(connectionsMutex);
103 | connections.emplace_back(conn);
104 | }
105 | }
106 | });
107 |
108 | for (unsigned fti = 0; fti < fireThreadCount; ++fti)
109 | {
110 | threads.emplace_back([&] {
111 | for (unsigned fi = 0; fi < fireCountPerThread; ++fi)
112 | {
113 | const size_t index = get_random_index(signalsCount);
114 | signals.at(index)();
115 | }
116 | });
117 | }
118 | for (auto& thread : threads)
119 | {
120 | thread.join();
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/FastSignals.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.27703.2035
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals", "libfastsignals\libfastsignals.vcxproj", "{32BD918F-EDBC-4057-A033-10DC361DA4A0}"
7 | EndProject
8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals_unit_tests", "tests\libfastsignals_unit_tests\libfastsignals_unit_tests.vcxproj", "{BAC23A51-8DC1-4589-940F-9923D8E12718}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033}"
11 | EndProject
12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libfastsignals_stress_tests", "tests\libfastsignals_stress_tests\libfastsignals_stress_tests.vcxproj", "{751DC150-1907-4D9F-8566-AA4E24FDFA64}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|x64 = Debug|x64
17 | Debug|x86 = Debug|x86
18 | Release|x64 = Release|x64
19 | Release|x86 = Release|x86
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x64.ActiveCfg = Debug|x64
23 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x64.Build.0 = Debug|x64
24 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x86.ActiveCfg = Debug|Win32
25 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Debug|x86.Build.0 = Debug|Win32
26 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x64.ActiveCfg = Release|x64
27 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x64.Build.0 = Release|x64
28 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x86.ActiveCfg = Release|Win32
29 | {32BD918F-EDBC-4057-A033-10DC361DA4A0}.Release|x86.Build.0 = Release|Win32
30 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x64.ActiveCfg = Debug|x64
31 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x64.Build.0 = Debug|x64
32 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x86.ActiveCfg = Debug|Win32
33 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Debug|x86.Build.0 = Debug|Win32
34 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x64.ActiveCfg = Release|x64
35 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x64.Build.0 = Release|x64
36 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x86.ActiveCfg = Release|Win32
37 | {BAC23A51-8DC1-4589-940F-9923D8E12718}.Release|x86.Build.0 = Release|Win32
38 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x64.ActiveCfg = Debug|x64
39 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x64.Build.0 = Debug|x64
40 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x86.ActiveCfg = Debug|Win32
41 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Debug|x86.Build.0 = Debug|Win32
42 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x64.ActiveCfg = Release|x64
43 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x64.Build.0 = Release|x64
44 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x86.ActiveCfg = Release|Win32
45 | {751DC150-1907-4D9F-8566-AA4E24FDFA64}.Release|x86.Build.0 = Release|Win32
46 | EndGlobalSection
47 | GlobalSection(SolutionProperties) = preSolution
48 | HideSolutionNode = FALSE
49 | EndGlobalSection
50 | GlobalSection(NestedProjects) = preSolution
51 | {BAC23A51-8DC1-4589-940F-9923D8E12718} = {6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033}
52 | {751DC150-1907-4D9F-8566-AA4E24FDFA64} = {6BBDE9AD-AF40-4DDF-8DA6-BEAD0A204033}
53 | EndGlobalSection
54 | GlobalSection(ExtensibilityGlobals) = postSolution
55 | SolutionGuid = {12A1931D-508E-41C2-BAC6-B68CC62A710E}
56 | EndGlobalSection
57 | EndGlobal
58 |
--------------------------------------------------------------------------------
/tests/libfastsignals_unit_tests/bind_weak_tests.cpp:
--------------------------------------------------------------------------------
1 | #include "catch2/catch.hpp"
2 | #include "libfastsignals/include/bind_weak.h"
3 |
4 | using namespace is::signals;
5 |
6 | namespace
7 | {
8 | class Testbed
9 | {
10 | public:
11 | Testbed(unsigned& counter)
12 | : m_pCounter(&counter)
13 | {
14 | }
15 |
16 | void IncrementNonConst()
17 | {
18 | ++(*m_pCounter);
19 | }
20 |
21 | void IncrementsConst() const
22 | {
23 | ++(*m_pCounter);
24 | }
25 |
26 | int ReflectInt(int value) const
27 | {
28 | return value;
29 | }
30 |
31 | private:
32 | unsigned* m_pCounter = nullptr;
33 | };
34 | } // namespace
35 |
36 | TEST_CASE("can bind const methods", "[bind_weak]")
37 | {
38 | unsigned counter = 0;
39 | auto pSharedBed = std::make_shared(counter);
40 | auto boundFn = bind_weak(&Testbed::IncrementNonConst, pSharedBed);
41 | REQUIRE(counter == 0u);
42 | boundFn();
43 | REQUIRE(counter == 1u);
44 | boundFn();
45 | REQUIRE(counter == 2u);
46 | pSharedBed = nullptr;
47 | boundFn();
48 | REQUIRE(counter == 2u);
49 | boundFn();
50 | REQUIRE(counter == 2u);
51 | }
52 |
53 | TEST_CASE("can bind non const methods", "[bind_weak]")
54 | {
55 | unsigned counter = 0;
56 | auto pSharedBed = std::make_shared(counter);
57 | auto boundFn = bind_weak(&Testbed::IncrementsConst, pSharedBed);
58 | REQUIRE(counter == 0u);
59 | boundFn();
60 | REQUIRE(counter == 1u);
61 | boundFn();
62 | REQUIRE(counter == 2u);
63 | pSharedBed = nullptr;
64 | boundFn();
65 | REQUIRE(counter == 2u);
66 | boundFn();
67 | REQUIRE(counter == 2u);
68 | }
69 |
70 | TEST_CASE("can bind method with argument value", "[bind_weak]")
71 | {
72 | unsigned counter = 0;
73 | auto pSharedBed = std::make_shared(counter);
74 | auto boundFn = bind_weak(&Testbed::ReflectInt, pSharedBed, 42);
75 | REQUIRE(boundFn() == 42);
76 | REQUIRE(boundFn() == 42);
77 | pSharedBed = nullptr;
78 | REQUIRE(boundFn() == 0);
79 | REQUIRE(boundFn() == 0);
80 | }
81 |
82 | TEST_CASE("copies value when bind method with argument const reference value", "[bind_weak]")
83 | {
84 | unsigned counter = 0;
85 | auto pSharedBed = std::make_shared(counter);
86 | auto makeBoundFn = [&]() {
87 | int value = 15;
88 | const int& valueRef = value;
89 | auto result = bind_weak(&Testbed::ReflectInt, pSharedBed, valueRef);
90 | value = 25;
91 | return result;
92 | };
93 |
94 | auto boundFn = makeBoundFn();
95 | REQUIRE(boundFn(42) == 15);
96 | REQUIRE(boundFn(42) == 15);
97 | pSharedBed = nullptr;
98 | REQUIRE(boundFn(42) == 0);
99 | REQUIRE(boundFn(42) == 0);
100 | }
101 |
102 | TEST_CASE("copies value when bind method with argument reference value", "[bind_weak]")
103 | {
104 | unsigned counter = 0;
105 | auto pSharedBed = std::make_shared(counter);
106 | auto makeBoundFn = [&]() {
107 | int value = 15;
108 | int& valueRef = value;
109 | auto result = bind_weak(&Testbed::ReflectInt, pSharedBed, valueRef);
110 | valueRef = 25;
111 | return result;
112 | };
113 |
114 | auto boundFn = makeBoundFn();
115 | REQUIRE(boundFn(42) == 15);
116 | REQUIRE(boundFn(42) == 15);
117 | pSharedBed = nullptr;
118 | REQUIRE(boundFn(42) == 0);
119 | REQUIRE(boundFn(42) == 0);
120 | }
121 |
122 | TEST_CASE("can bind method with placeholder", "[bind_weak]")
123 | {
124 | unsigned counter = 0;
125 | auto pSharedBed = std::make_shared(counter);
126 | auto boundFn = bind_weak(&Testbed::ReflectInt, pSharedBed, std::placeholders::_1);
127 | REQUIRE(boundFn(42) == 42);
128 | REQUIRE(boundFn(42) == 42);
129 | pSharedBed = nullptr;
130 | REQUIRE(boundFn(42) == 0);
131 | REQUIRE(boundFn(42) == 0);
132 | }
133 |
--------------------------------------------------------------------------------
/.clang-format:
--------------------------------------------------------------------------------
1 | ---
2 | Language: Cpp
3 | # BasedOnStyle: WebKit
4 | AccessModifierOffset: -4
5 | AlignAfterOpenBracket: DontAlign
6 | AlignConsecutiveAssignments: false
7 | AlignConsecutiveDeclarations: false
8 | AlignEscapedNewlinesLeft: false
9 | AlignOperands: false
10 | AlignTrailingComments: false
11 | AllowAllParametersOfDeclarationOnNextLine: true
12 | AllowShortBlocksOnASingleLine: false
13 | AllowShortCaseLabelsOnASingleLine: false
14 | AllowShortFunctionsOnASingleLine: All
15 | AllowShortIfStatementsOnASingleLine: false
16 | AllowShortLoopsOnASingleLine: false
17 | AlwaysBreakAfterDefinitionReturnType: None
18 | AlwaysBreakAfterReturnType: None
19 | AlwaysBreakBeforeMultilineStrings: false
20 | AlwaysBreakTemplateDeclarations: false
21 | BinPackArguments: true
22 | BinPackParameters: true
23 | BraceWrapping:
24 | AfterClass: true
25 | AfterControlStatement: true
26 | AfterEnum: true
27 | AfterFunction: true
28 | AfterNamespace: true
29 | AfterObjCDeclaration: false
30 | AfterStruct: true
31 | AfterUnion: true
32 | BeforeCatch: true
33 | BeforeElse: true
34 | IndentBraces: false
35 | BreakBeforeBinaryOperators: All
36 | BreakBeforeBraces: Custom
37 | BreakBeforeTernaryOperators: true
38 | BreakConstructorInitializersBeforeComma: true
39 | BreakAfterJavaFieldAnnotations: false
40 | BreakStringLiterals: true
41 | ColumnLimit: 0
42 | CommentPragmas: '^ IWYU pragma:'
43 | BreakBeforeInheritanceComma: true
44 | FixNamespaceComments: true
45 | ConstructorInitializerAllOnOneLineOrOnePerLine: false
46 | ConstructorInitializerIndentWidth: 4
47 | ContinuationIndentWidth: 4
48 | Cpp11BracedListStyle: false
49 | DerivePointerAlignment: false
50 | DisableFormat: false
51 | ExperimentalAutoDetectBinPacking: false
52 | # FixNamespaceComments: false
53 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
54 | IncludeCategories:
55 | - Regex: '^"(stdafx|PrecompiledHeader)'
56 | Priority: -2
57 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
58 | Priority: 4
59 | - Regex: '^(<|"(gtest|isl|json)/)'
60 | Priority: 3
61 | - Regex: '.*'
62 | Priority: 2
63 | IncludeIsMainRegex: '$'
64 | IndentCaseLabels: false
65 | IndentWidth: 4
66 | IndentWrappedFunctionNames: false
67 | JavaScriptQuotes: Leave
68 | JavaScriptWrapImports: true
69 | KeepEmptyLinesAtTheStartOfBlocks: true
70 | MacroBlockBegin: 'BEGIN_MESSAGE_MAP_CUSTOM$|BEGIN_MSG_MAP$|BEGIN_SINK_MAP$|BEGIN_MESSAGE_MAP$|BOOST_FIXTURE_TEST_SUITE$|BOOST_AUTO_TEST_SUITE$|BEGIN_DLGRESIZE_MAP$|BEGIN_MSG_MAP_EX$|BEGIN_DDX_MAP$|BEGIN_COM_MAP$|BEGIN_CONNECTION_POINT_MAP$|WTL_BEGIN_LAYOUT_MAP$|WTL_BEGIN_LAYOUT_CONTAINER$'
71 | MacroBlockEnd: 'END_MESSAGE_MAP_CUSTOM$|END_MSG_MAP$|END_SINK_MAP$|END_MESSAGE_MAP$|BOOST_AUTO_TEST_SUITE_END$|END_DLGRESIZE_MAP$|END_DDX_MAP$|END_COM_MAP$|END_CONNECTION_POINT_MAP$|WTL_END_LAYOUT_MAP$|WTL_END_LAYOUT_CONTAINER$'
72 | MaxEmptyLinesToKeep: 1
73 | NamespaceIndentation: None
74 | ObjCBlockIndentWidth: 4
75 | ObjCSpaceAfterProperty: true
76 | ObjCSpaceBeforeProtocolList: true
77 | PenaltyBreakBeforeFirstCallParameter: 19
78 | PenaltyBreakComment: 300
79 | PenaltyBreakFirstLessLess: 120
80 | PenaltyBreakString: 1000
81 | PenaltyExcessCharacter: 100
82 | PenaltyReturnTypeOnItsOwnLine: 10000000
83 | PointerAlignment: Left
84 | ReflowComments: true
85 | SortIncludes: true
86 | SortUsingDeclarations: true
87 | SpaceAfterCStyleCast: false
88 | SpaceAfterTemplateKeyword: true
89 | SpaceBeforeAssignmentOperators: true
90 | SpaceBeforeParens: ControlStatements
91 | SpaceInEmptyParentheses: false
92 | SpacesBeforeTrailingComments: 1
93 | SpacesInAngles: false
94 | SpacesInContainerLiterals: true
95 | SpacesInCStyleCastParentheses: false
96 | SpacesInParentheses: false
97 | SpacesInSquareBrackets: false
98 | Standard: Cpp11
99 | TabWidth: 4
100 | UseTab: Always
101 | IndentPPDirectives: AfterHash
102 | ...
103 |
--------------------------------------------------------------------------------
/libfastsignals/include/connection.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "signal_impl.h"
4 |
5 | namespace is::signals
6 | {
7 |
8 | // Connection keeps link between signal and slot and can disconnect them.
9 | // Disconnect operation is thread-safe: any thread can disconnect while
10 | // slots called on other thread.
11 | // This class itself is not thread-safe: you can't use the same connection
12 | // object from different threads at the same time.
13 | class connection
14 | {
15 | public:
16 | connection() noexcept;
17 | explicit connection(detail::signal_impl_weak_ptr storage, uint64_t id) noexcept;
18 | connection(const connection& other) noexcept;
19 | connection& operator=(const connection& other) noexcept;
20 | connection(connection&& other) noexcept;
21 | connection& operator=(connection&& other) noexcept;
22 |
23 | bool connected() const noexcept;
24 | void disconnect() noexcept;
25 |
26 | protected:
27 | detail::signal_impl_weak_ptr m_storage;
28 | uint64_t m_id = 0;
29 | };
30 |
31 | // Connection class that supports blocking callback execution
32 | class advanced_connection : public connection
33 | {
34 | public:
35 | struct advanced_connection_impl
36 | {
37 | void block() noexcept;
38 | void unblock() noexcept;
39 | bool is_blocked() const noexcept;
40 |
41 | private:
42 | std::atomic m_blockCounter = ATOMIC_VAR_INIT(0);
43 | };
44 | using impl_ptr = std::shared_ptr;
45 |
46 | advanced_connection() noexcept;
47 | explicit advanced_connection(connection&& conn, impl_ptr&& impl) noexcept;
48 | advanced_connection(const advanced_connection&) noexcept;
49 | advanced_connection& operator=(const advanced_connection&) noexcept;
50 | advanced_connection(advanced_connection&& other) noexcept;
51 | advanced_connection& operator=(advanced_connection&& other) noexcept;
52 |
53 | protected:
54 | impl_ptr m_impl;
55 | };
56 |
57 | // Blocks advanced connection, so its callback will not be executed
58 | class shared_connection_block
59 | {
60 | public:
61 | shared_connection_block(const advanced_connection& connection = advanced_connection(), bool initially_blocked = true) noexcept;
62 | shared_connection_block(const shared_connection_block& other) noexcept;
63 | shared_connection_block(shared_connection_block&& other) noexcept;
64 | shared_connection_block& operator=(const shared_connection_block& other) noexcept;
65 | shared_connection_block& operator=(shared_connection_block&& other) noexcept;
66 | ~shared_connection_block();
67 |
68 | void block() noexcept;
69 | void unblock() noexcept;
70 | bool blocking() const noexcept;
71 |
72 | private:
73 | void increment_if_blocked() const noexcept;
74 |
75 | std::weak_ptr m_connection;
76 | std::atomic m_blocked = ATOMIC_VAR_INIT(false);
77 | };
78 |
79 | // Scoped connection keeps link between signal and slot and disconnects them in destructor.
80 | // Scoped connection is movable, but not copyable.
81 | class scoped_connection : public connection
82 | {
83 | public:
84 | scoped_connection() noexcept;
85 | scoped_connection(const connection& conn) noexcept;
86 | scoped_connection(connection&& conn) noexcept;
87 | scoped_connection(const advanced_connection& conn) = delete;
88 | scoped_connection(advanced_connection&& conn) noexcept = delete;
89 | scoped_connection(const scoped_connection&) = delete;
90 | scoped_connection& operator=(const scoped_connection&) = delete;
91 | scoped_connection(scoped_connection&& other) noexcept;
92 | scoped_connection& operator=(scoped_connection&& other) noexcept;
93 | ~scoped_connection();
94 |
95 | connection release() noexcept;
96 | };
97 |
98 | // scoped connection for advanced connections
99 | class advanced_scoped_connection : public advanced_connection
100 | {
101 | public:
102 | advanced_scoped_connection() noexcept;
103 | advanced_scoped_connection(const advanced_connection& conn) noexcept;
104 | advanced_scoped_connection(advanced_connection&& conn) noexcept;
105 | advanced_scoped_connection(const advanced_scoped_connection&) = delete;
106 | advanced_scoped_connection& operator=(const advanced_scoped_connection&) = delete;
107 | advanced_scoped_connection(advanced_scoped_connection&& other) noexcept;
108 | advanced_scoped_connection& operator=(advanced_scoped_connection&& other) noexcept;
109 | ~advanced_scoped_connection();
110 |
111 | advanced_connection release() noexcept;
112 | };
113 |
114 | } // namespace is::signals
115 |
--------------------------------------------------------------------------------
/libfastsignals/include/signal.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "combiners.h"
4 | #include "connection.h"
5 | #include "function.h"
6 | #include "signal_impl.h"
7 | #include "type_traits.h"
8 | #include
9 |
10 | #if defined(_MSC_VER)
11 | # include "msvc_autolink.h"
12 | #endif
13 |
14 | namespace is::signals
15 | {
16 | template class Combiner = optional_last_value>
17 | class signal;
18 |
19 | struct advanced_tag
20 | {
21 | };
22 |
23 | /// Signal allows to fire events to many subscribers (slots).
24 | /// In other words, it implements one-to-many relation between event and listeners.
25 | /// Signal implements observable object from Observable pattern.
26 | template class Combiner>
27 | class signal : private not_directly_callable
28 | {
29 | public:
30 | using signature_type = Return(signal_arg_t...);
31 | using slot_type = function;
32 | using combiner_type = Combiner;
33 | using result_type = typename combiner_type::result_type;
34 |
35 | signal()
36 | : m_slots(std::make_shared())
37 | {
38 | }
39 |
40 | /// No copy construction
41 | signal(const signal&) = delete;
42 |
43 | /// Moves signal from other. Any operations on other except destruction, move, and swap are invalid
44 | signal(signal&& other) = default;
45 |
46 | /// No copy assignment
47 | signal& operator=(const signal&) = delete;
48 |
49 | /// Moves signal from other. Any operations on other except destruction, move, and swap are invalid
50 | signal& operator=(signal&& other) = default;
51 |
52 | /**
53 | * connect(slot) method subscribes slot to signal emission event.
54 | * Each time you call signal as functor, all slots are also called with given arguments.
55 | * @returns connection - object which manages signal-slot connection lifetime
56 | */
57 | connection connect(slot_type slot)
58 | {
59 | const uint64_t id = m_slots->add(slot.release());
60 | return connection(m_slots, id);
61 | }
62 |
63 | /**
64 | * connect(slot, advanced_tag) method subscribes slot to signal emission event with the ability to temporarily block slot execution
65 | * Each time you call signal as functor, all non-blocked slots are also called with given arguments.
66 | * You can temporarily block slot execution using shared_connection_block
67 | * @returns advanced_connection - object which manages signal-slot connection lifetime
68 | */
69 | advanced_connection connect(slot_type slot, advanced_tag)
70 | {
71 | static_assert(std::is_void_v, "Advanced connect can only be used with slots returning void (implementation limitation)");
72 | auto conn_impl = std::make_shared();
73 | slot_type slot_impl = [this, slot, weak_conn_impl = std::weak_ptr(conn_impl)](signal_arg_t... args) {
74 | auto conn_impl = weak_conn_impl.lock();
75 | if (!conn_impl || !conn_impl->is_blocked())
76 | {
77 | slot(args...);
78 | }
79 | };
80 | auto conn = connect(std::move(slot_impl));
81 | return advanced_connection(std::move(conn), std::move(conn_impl));
82 | }
83 |
84 | /**
85 | * disconnect_all_slots() method disconnects all slots from signal emission event.
86 | */
87 | void disconnect_all_slots() noexcept
88 | {
89 | m_slots->remove_all();
90 | }
91 |
92 | /**
93 | * num_slots() method returns number of slots attached to this singal
94 | */
95 | [[nodiscard]] std::size_t num_slots() const noexcept
96 | {
97 | return m_slots->count();
98 | }
99 |
100 | /**
101 | * empty() method returns true if signal has any slots attached
102 | */
103 | [[nodiscard]] bool empty() const noexcept
104 | {
105 | return m_slots->count() == 0;
106 | }
107 |
108 | /**
109 | * operator(args...) calls all slots connected to this signal.
110 | * Logically, it fires signal emission event.
111 | */
112 | result_type operator()(signal_arg_t... args) const
113 | {
114 | return detail::signal_impl_ptr(m_slots)->invoke...>(args...);
115 | }
116 |
117 | void swap(signal& other) noexcept
118 | {
119 | m_slots.swap(other.m_slots);
120 | }
121 |
122 | /**
123 | * Allows using signals as slots for another signal
124 | */
125 | operator slot_type() const noexcept
126 | {
127 | return [weakSlots = detail::signal_impl_weak_ptr(m_slots)](signal_arg_t... args) {
128 | if (auto slots = weakSlots.lock())
129 | {
130 | return slots->invoke...>(args...);
131 | }
132 | };
133 | }
134 |
135 | private:
136 | detail::signal_impl_ptr m_slots;
137 | };
138 |
139 | } // namespace is::signals
140 |
141 | namespace std
142 | {
143 |
144 | // free swap function, findable by ADL
145 | template class Combiner>
146 | void swap(
147 | ::is::signals::signal& sig1,
148 | ::is::signals::signal& sig2)
149 | {
150 | sig1.swap(sig2);
151 | }
152 |
153 | } // namespace std
154 |
--------------------------------------------------------------------------------
/libfastsignals/include/function_detail.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | namespace is::signals::detail
9 | {
10 | /// Buffer for callable object in-place construction,
11 | /// helps to implement Small Buffer Optimization.
12 | static constexpr size_t inplace_buffer_size = (sizeof(int) == sizeof(void*) ? 8 : 6) * sizeof(void*);
13 |
14 | /// Structure that has size enough to keep type "T" including vtable.
15 | template
16 | struct type_container
17 | {
18 | T data;
19 | };
20 |
21 | /// Type that has enough space to keep type "T" (including vtable) with suitable alignment.
22 | using function_buffer_t = std::aligned_storage_t;
23 |
24 | /// Constantly is true if callable fits function buffer, false otherwise.
25 | template
26 | inline constexpr bool fits_inplace_buffer = (sizeof(type_container) <= inplace_buffer_size);
27 |
28 | // clang-format off
29 | /// Constantly is true if callable fits function buffer and can be safely moved, false otherwise
30 | template
31 | inline constexpr bool can_use_inplace_buffer =
32 | fits_inplace_buffer &&
33 | std::is_nothrow_move_constructible_v;
34 | // clang format on
35 |
36 | /// Type that is suitable to keep copy of callable object.
37 | /// - equal to pointer-to-function if Callable is pointer-to-function
38 | /// - otherwise removes const/volatile and references to allow copying callable.
39 | template
40 | using callable_copy_t = std::conditional_t>,
41 | Callable,
42 | std::remove_cv_t>>;
43 |
44 | class base_function_proxy
45 | {
46 | public:
47 | virtual ~base_function_proxy() = default;
48 | virtual base_function_proxy* clone(void* buffer) const = 0;
49 | virtual base_function_proxy* move(void* buffer) noexcept = 0;
50 | };
51 |
52 | template
53 | class function_proxy;
54 |
55 | template
56 | class function_proxy : public base_function_proxy
57 | {
58 | public:
59 | virtual Return operator()(Arguments&&...) = 0;
60 | };
61 |
62 | template
63 | class function_proxy_impl final : public function_proxy
64 | {
65 | public:
66 | // If you see this error, probably your function returns value and you're trying to
67 | // connect it to `signal`. Just remove return value from callback.
68 | static_assert(std::is_same_v, Return>,
69 | "cannot construct function<> class from callable object with different return type");
70 |
71 | template
72 | explicit function_proxy_impl(FunctionObject&& function)
73 | : m_callable(std::forward(function))
74 | {
75 | }
76 |
77 | Return operator()(Arguments&&... args) final
78 | {
79 | return m_callable(std::forward(args)...);
80 | }
81 |
82 | base_function_proxy* clone(void* buffer) const final
83 | {
84 | if constexpr (can_use_inplace_buffer)
85 | {
86 | return new (buffer) function_proxy_impl(*this);
87 | }
88 | else
89 | {
90 | (void)buffer;
91 | return new function_proxy_impl(*this);
92 | }
93 | }
94 |
95 | base_function_proxy* move(void* buffer) noexcept final
96 | {
97 | if constexpr (can_use_inplace_buffer)
98 | {
99 | base_function_proxy* moved = new (buffer) function_proxy_impl(std::move(*this));
100 | this->~function_proxy_impl();
101 | return moved;
102 | }
103 | else
104 | {
105 | (void)buffer;
106 | return this;
107 | }
108 | }
109 |
110 | private:
111 | callable_copy_t m_callable;
112 | };
113 |
114 | template
115 | inline constexpr bool is_noexcept_packed_function_init = can_use_inplace_buffer>;
116 |
117 | class packed_function final
118 | {
119 | public:
120 | packed_function() = default;
121 | packed_function(packed_function&& other) noexcept;
122 | packed_function(const packed_function& other);
123 | packed_function& operator=(packed_function&& other) noexcept;
124 | packed_function& operator=(const packed_function& other);
125 | ~packed_function() noexcept;
126 |
127 | // Initializes packed function.
128 | // Cannot be called without reset().
129 | template
130 | void init(Callable&& function) noexcept(is_noexcept_packed_function_init)
131 | {
132 | using proxy_t = function_proxy_impl;
133 |
134 | assert(m_proxy == nullptr);
135 | if constexpr (can_use_inplace_buffer)
136 | {
137 | m_proxy = new (&m_buffer) proxy_t{ std::forward(function) };
138 | }
139 | else
140 | {
141 | m_proxy = new proxy_t{ std::forward(function) };
142 | }
143 | }
144 |
145 | template
146 | function_proxy& get() const
147 | {
148 | return static_cast&>(unwrap());
149 | }
150 |
151 | void reset() noexcept;
152 |
153 | private:
154 | base_function_proxy* move_proxy_from(packed_function&& other) noexcept;
155 | base_function_proxy* clone_proxy_from(const packed_function &other);
156 | base_function_proxy& unwrap() const;
157 | bool is_buffer_allocated() const noexcept;
158 |
159 | function_buffer_t m_buffer[1] = {};
160 | base_function_proxy* m_proxy = nullptr;
161 | };
162 |
163 | } // namespace is::signals::detail
164 |
--------------------------------------------------------------------------------
/docs/migration-from-boost-signals2.md:
--------------------------------------------------------------------------------
1 | # Migration from Boost.Signals2
2 |
3 | This guide helps to migrate large codebase from Boost.Signals2 to `FastSignals` signals/slots library. It helps to solve known migration issues in the right way.
4 |
5 | During migrations, you will probably face with following things:
6 |
7 | * You code uses `boost::signals2::` namespace and `` header directly
8 | * You code uses third-party headers included implicitly by the `` header
9 |
10 | ## Reasons migrate from Boost.Signals2 to FastSignals
11 |
12 | FastSignals API mostly compatible with Boost.Signals2 - there are differences, and all differences has their reasons explained below.
13 |
14 | Comparing to Boost.Signals2, FastSignals has following pros:
15 |
16 | * FastSignals is not header-only - so binary code will be more compact
17 | * FastSignals implemented using C++17 with variadic templates, `constexpr if` and other modern metaprogramming techniques - so it compiles faster and, again, binary code will be more compact
18 | * FastSignals probably will faster than Boost.Signals2 for your codebase because with FastSignals you don't pay for things that you don't use, with one exception: you always pay for the multithreading support
19 |
20 | ## Step 1: Create header with aliases
21 |
22 | ## Step 2: Rebuild and fix compile errors
23 |
24 | ### 2.1 Add missing includes
25 |
26 | Boost.Signals2 is header-only library. It includes a lot of STL/Boost stuff while FastSignals does not:
27 |
28 | ```cpp
29 | #include
30 | // Also includes std::map, boost::variant, boost::optional, etc.
31 |
32 | // Compiled OK even without `#include