diff options
author | Kjell Ahlstedt <kjellahlstedt@gmail.com> | 2022-05-31 16:57:50 +0200 |
---|---|---|
committer | Kjell Ahlstedt <kjellahlstedt@gmail.com> | 2022-05-31 16:57:50 +0200 |
commit | 560f800e45329ad55b1f5b0a05a80ad63164da48 (patch) | |
tree | 097b5ec9f0989dcc931c54d1cfd6c18edd7d81dd | |
parent | c07d75d47dd0374e437c8ba6f4c4816d4b7f89e1 (diff) | |
download | sigc++-560f800e45329ad55b1f5b0a05a80ad63164da48.tar.gz |
Add trackable_signal_with_accumulator and trackable_signal
trackable_signal_with_accumulator derives from trackable.
A slot made with trackable_signal_with_accumulator::make_slot() is
automatically disconnected when the signal is deleted, as in sigc++2.
Fixes #80
-rw-r--r-- | sigc++/signal.h | 291 | ||||
-rw-r--r-- | sigc++/signal_base.h | 5 | ||||
-rw-r--r-- | tests/test_accumulated.cc | 6 | ||||
-rw-r--r-- | tests/test_signal.cc | 18 |
4 files changed, 308 insertions, 12 deletions
diff --git a/sigc++/signal.h b/sigc++/signal.h index 9d6143c..8cddf10 100644 --- a/sigc++/signal.h +++ b/sigc++/signal.h @@ -372,7 +372,7 @@ public: } /* namespace internal */ /** Signal declaration. - * signal_with_accumulator can be used to connect() slots that are invoked + * %signal_with_accumulator can be used to connect() slots that are invoked * during subsequent calls to emit(). Any functor or slot * can be passed into connect(). It is converted into a slot * implicitly. @@ -462,10 +462,11 @@ public: /** Creates a functor that calls emit() on this signal. * - * @note %sigc::signal does not derive from sigc::trackable in sigc++3. - * If you connect the returned functor (calling %emit() on signal1) to - * another signal (signal2) and then delete signal1, you must manually + * @note %sigc::signal does not derive from sigc::trackable. + * If you connect the returned functor that calls %emit() on signal1, + * to another signal (signal2) and then delete signal1, you must manually * disconnect signal1 from signal2 before you delete signal1. + * Alternatively, make a slot of a sigc::trackable_signal. * * @code * sigc::mem_fun(mysignal, &sigc::signal_with_accumulator::emit) @@ -517,7 +518,8 @@ public: * * The template arguments determine the function signature of * the emit() function: - * - @e T_return The desired return type of the emit() function. * - @e T_arg Argument types used in + * - @e T_return The desired return type of the emit() function. + * - @e T_arg Argument types used in * the definition of emit(). * * For instance, to declare a signal whose connected slot returns void and takes @@ -531,7 +533,7 @@ public: * @par Example: * @code * void foo(int) {} - * sigc::signal<void, long> sig; + * sigc::signal<void(long)> sig; * sig.connect(sigc::ptr_fun(&foo)); * sig.emit(19); * @endcode @@ -625,6 +627,283 @@ public: } }; +// TODO: When we can break ABI, let signal_base derive from trackable, as in sigc++2, +// and delete trackable_signal_with_accumulator and trackable_signal. +// https://github.com/libsigcplusplus/libsigcplusplus/issues/80 + +/** Signal declaration. + * %trackable_signal_with_accumulator can be used to connect() slots that are invoked + * during subsequent calls to emit(). Any functor or slot + * can be passed into connect(). It is converted into a slot implicitly. + * + * If you want to connect one signal to another, use make_slot() + * to retrieve a functor that emits the signal when invoked. + * + * Be careful if you directly pass one signal into the connect() + * method of another: a shallow copy of the signal is made and + * the signal's slots are not disconnected until both the signal + * and its clone are destroyed, which is probably not what you want! + * + * The following template arguments are used: + * - @e T_return The desired return type for the emit() function (may be overridden by the + * accumulator). + * - @e T_arg Argument types used in the definition of emit(). + * - @e T_accumulator The accumulator type used for emission. The default + * @p void means that no accumulator should be used, for example if signal + * emission returns the return value of the last slot invoked. + * + * @newin{3,4} + * + * @ingroup signal + */ +template<typename T_return, typename T_accumulator, typename... T_arg> +class trackable_signal_with_accumulator +: public signal_base +, public trackable +{ +public: + using slot_type = slot<T_return(T_arg...)>; + + /** Add a slot to the list of slots. + * Any functor or slot may be passed into connect(). + * It will be converted into a slot implicitly. + * The returned connection may be stored for disconnection + * of the slot at some later point. It stays valid until + * the slot is disconnected from the signal. + * std::function<> and C++11 lambda expressions are functors. + * These are examples of functors that can be connected to a signal. + * + * %std::bind() creates a functor, but this functor typically has an + * %operator()() which is a variadic template. + * Our functor_trait can't deduce the result type + * of such a functor. If you first assign the return value of %std::bind() + * to a std::function, you can connect the std::function to a signal. + * + * @param slot_ The slot to add to the list of slots. + * @return A connection. + */ + connection connect(const slot_type& slot_) + { + auto iter = signal_base::connect(slot_); + auto& slot_base = *iter; + return connection(slot_base); + } + + /** Add a slot to the list of slots. + * @see connect(const slot_type& slot_). + */ + connection connect(slot_type&& slot_) + { + auto iter = signal_base::connect(std::move(slot_)); + auto& slot_base = *iter; + return connection(slot_base); + } + + /** Triggers the emission of the signal. + * During signal emission all slots that have been connected + * to the signal are invoked unless they are manually set into + * a blocking state. The parameters are passed on to the slots. + * If @e T_accumulated is not @p void, an accumulator of this type + * is used to process the return values of the slot invocations. + * Otherwise, the return value of the last slot invoked is returned. + * @param a Arguments to be passed on to the slots. + * @return The accumulated return values of the slot invocations. + */ + decltype(auto) emit(type_trait_take_t<T_arg>... a) const + { + using emitter_type = internal::signal_emit<T_return, T_accumulator, T_arg...>; + return emitter_type::emit(impl_, std::forward<type_trait_take_t<T_arg>>(a)...); + } + + /** Triggers the emission of the signal (see emit()). */ + decltype(auto) operator()(type_trait_take_t<T_arg>... a) const + { + return emit(std::forward<type_trait_take_t<T_arg>>(a)...); + } + + /** Creates a functor that calls emit() on this signal. + * + * @code + * sigc::mem_fun(mysignal, &sigc::trackable_signal_with_accumulator::emit) + * @endcode + * yields the same result. + * @return A functor that calls emit() on this signal. + */ + decltype(auto) make_slot() const + { + // TODO: Instead use std::invoke_result<> on the static emitter_type::emit() + using result_type = typename internal::member_method_result< + decltype(&trackable_signal_with_accumulator::emit)>::type; + return bound_mem_functor<result_type (trackable_signal_with_accumulator::*)( + type_trait_take_t<T_arg>...) const, + type_trait_take_t<T_arg>...>(*this, &trackable_signal_with_accumulator::emit); + } + + trackable_signal_with_accumulator() = default; + + trackable_signal_with_accumulator(const trackable_signal_with_accumulator& src) + : signal_base(src), trackable(src) + { + } + + trackable_signal_with_accumulator(trackable_signal_with_accumulator&& src) + : signal_base(std::move(src)), trackable(std::move(src)) + { + } + + trackable_signal_with_accumulator& operator=(const trackable_signal_with_accumulator& src) + { + signal_base::operator=(src); + // Don't call trackable::operator=(src). + // It calls notify_callbacks(). This signal is not destroyed. + return *this; + } + + trackable_signal_with_accumulator& operator=(trackable_signal_with_accumulator&& src) + { + signal_base::operator=(std::move(src)); + if (src.impl_ != impl_) + src.notify_callbacks(); + // Don't call trackable::operator=(std::move(src)). + // It calls notify_callbacks(). This signal is not destroyed. + return *this; + } +}; + +/** %trackable_signal can be used to connect() slots that are invoked + * during subsequent calls to emit(). Any functor or slot + * can be passed into connect(). It is converted into a slot + * implicitly. + * + * If you want to connect one signal to another, use make_slot() + * to retrieve a functor that emits the signal when invoked. + * + * Be careful if you directly pass one signal into the connect() + * method of another: a shallow copy of the signal is made and + * the signal's slots are not disconnected until both the signal + * and its clone are destroyed, which is probably not what you want! + * + * The template arguments determine the function signature of + * the emit() function: + * - @e T_return The desired return type of the emit() function. + * - @e T_arg Argument types used in + * the definition of emit(). + * + * For instance, to declare a %trackable_signal whose connected slot returns void and takes + * two parameters of bool and int: + * @code + * sigc::trackable_signal<void(bool, int)> some_signal; + * @endcode + * + * To specify an accumulator type the nested class trackable_signal::accumulated can be used. + * + * @par Example: + * @code + * void foo(int) {} + * sigc::trackable_signal<void(long)> sig; + * sig.connect(sigc::ptr_fun(&foo)); + * sig.emit(19); + * @endcode + * + * @newin{3,4} + * + * @ingroup signal + */ +#ifndef DOXYGEN_SHOULD_SKIP_THIS +template<typename T_return, typename... T_arg> +class trackable_signal; +#endif // DOXYGEN_SHOULD_SKIP_THIS + +template<typename T_return, typename... T_arg> +class trackable_signal<T_return(T_arg...)> +: public trackable_signal_with_accumulator<T_return, void, T_arg...> +{ +public: + using accumulator_type = void; + + /** Like @ref sigc::trackable_signal<T_return(T_arg...)> "sigc::trackable_signal" + * but the additional template parameter @e T_accumulator defines the accumulator + * type that should be used. + * + * An accumulator is a functor that uses a pair of special iterators + * to step through a list of slots and calculate a return value + * from the results of the slot invocations. The iterators' operator*() + * executes the slot. The return value is buffered, so that in an expression + * like @code a = (*i) * (*i); @endcode the slot is executed only once. + * + * @par Example 1: + * This accumulator calculates the arithmetic mean value: + * @code + * struct arithmetic_mean_accumulator + * { + * template<typename T_iterator> + * double operator()(T_iterator first, T_iterator last) const + * { + * double value_ = 0; + * int n_ = 0; + * for (; first != last; ++first, ++n_) + * value_ += *first; + * return value_ / n_; + * } + * }; + * @endcode + * + * @par Example 2: + * This accumulator stops signal emission when a slot returns zero: + * @code + * struct interruptable_accumulator + * { + * template<typename T_iterator> + * bool operator()(T_iterator first, T_iterator last) const + * { + * for (; first != last; ++first, ++n_) + * if (!*first) return false; + * return true; + * } + * }; + * @endcode + * + * @newin{3,4} + * + * @ingroup signal + */ + template<typename T_accumulator> + class accumulated : public trackable_signal_with_accumulator<T_return, T_accumulator, T_arg...> + { + public: + accumulated() = default; + accumulated(const accumulated& src) + : trackable_signal_with_accumulator<T_return, T_accumulator, T_arg...>(src) + { + } + }; + + trackable_signal() = default; + + trackable_signal(const trackable_signal& src) + : trackable_signal_with_accumulator<T_return, accumulator_type, T_arg...>(src) + { + } + + trackable_signal(trackable_signal&& src) + : trackable_signal_with_accumulator<T_return, accumulator_type, T_arg...>(std::move(src)) + { + } + + trackable_signal& operator=(const trackable_signal& src) + { + trackable_signal_with_accumulator<T_return, accumulator_type, T_arg...>::operator=(src); + return *this; + } + + trackable_signal& operator=(trackable_signal&& src) + { + trackable_signal_with_accumulator<T_return, accumulator_type, T_arg...>::operator=( + std::move(src)); + return *this; + } +}; + } /* namespace sigc */ #endif /* SIGC_SIGNAL_H */ diff --git a/sigc++/signal_base.h b/sigc++/signal_base.h index 8391782..d21e4b3 100644 --- a/sigc++/signal_base.h +++ b/sigc++/signal_base.h @@ -266,9 +266,10 @@ protected: * @ref sigc::signal_with_accumulator::connect() "sigc::signal::connect()". */ -// TODO: When we can break ABI, let signal_base derive from trackable again. -// It does in sigc++2. Otherwise the slot returned from signal::make_slot() +// TODO: When we can break ABI, let signal_base derive from trackable again, +// as in sigc++2. Otherwise the slot returned from signal::make_slot() // is not automatically disconnected when the signal is deleted. +// And delete trackable_signal_with_accumulator and trackable_signal. // https://github.com/libsigcplusplus/libsigcplusplus/issues/80 /** Base class for the @ref sigc::signal<T_return(T_arg...)> "sigc::signal" template. diff --git a/tests/test_accumulated.cc b/tests/test_accumulated.cc index f010b1c..39122b5 100644 --- a/tests/test_accumulated.cc +++ b/tests/test_accumulated.cc @@ -79,10 +79,11 @@ test_empty_signal() util->check_result(result_stream, "Vector result (empty slot list): empty"); } +template<typename T_signal> void test_mean() { - sigc::signal<int(int)>::accumulated<arithmetic_mean_accumulator> sig; + typename T_signal::accumulated<arithmetic_mean_accumulator> sig; A a; sig.connect(sigc::ptr_fun(&foo)); @@ -137,7 +138,8 @@ main(int argc, char* argv[]) return util->get_result_and_delete_instance() ? EXIT_SUCCESS : EXIT_FAILURE; test_empty_signal(); - test_mean(); + test_mean<sigc::signal<int(int)>>(); + test_mean<sigc::trackable_signal<int(int)>>(); test_vector_accumulator(); return util->get_result_and_delete_instance() ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/tests/test_signal.cc b/tests/test_signal.cc index d050558..ff197a4 100644 --- a/tests/test_signal.cc +++ b/tests/test_signal.cc @@ -5,6 +5,7 @@ #include "testutilities.h" #include <sigc++/trackable.h> #include <sigc++/signal.h> +#include <memory> namespace { @@ -62,11 +63,12 @@ bar(float i) return 1; } +template<typename T_signal> void test_auto_disconnection() { // signal - sigc::signal<int(int)> sig; + T_signal sig; // connect some slots before emitting & test auto-disconnection { @@ -110,6 +112,17 @@ test_make_slot() sig2.connect(sig.make_slot()); sig2(3); util->check_result(result_stream, "foo(int 3) bar(float 3) foo(int 3) "); + + // Delete a trackable_signal that has been connected to sig2. + sig2.clear(); + sig2.connect(sigc::ptr_fun(&bar)); + auto sig3 = std::make_unique<sigc::trackable_signal<int(int)>>(); + sig3->connect(sigc::ptr_fun(&foo)); + sig2.connect(sig3->make_slot()); + sig2(2); + sig3.reset(); + sig2(42); + util->check_result(result_stream, "bar(float 2) foo(int 2) bar(float 42) "); } void @@ -158,7 +171,8 @@ main(int argc, char* argv[]) test_empty_signal(); test_simple(); - test_auto_disconnection(); + test_auto_disconnection<sigc::signal<int(int)>>(); + test_auto_disconnection<sigc::trackable_signal<int(int)>>(); test_reference(); test_make_slot(); test_clear_called_in_signal_handler(); |