Skip to content

Commit 560f800

Browse files
committed
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
1 parent c07d75d commit 560f800

File tree

4 files changed

+308
-12
lines changed

4 files changed

+308
-12
lines changed

sigc++/signal.h

Lines changed: 285 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ struct signal_emit<void, void, T_arg...>
372372
} /* namespace internal */
373373

374374
/** Signal declaration.
375-
* signal_with_accumulator can be used to connect() slots that are invoked
375+
* %signal_with_accumulator can be used to connect() slots that are invoked
376376
* during subsequent calls to emit(). Any functor or slot
377377
* can be passed into connect(). It is converted into a slot
378378
* implicitly.
@@ -462,10 +462,11 @@ class signal_with_accumulator : public signal_base
462462

463463
/** Creates a functor that calls emit() on this signal.
464464
*
465-
* @note %sigc::signal does not derive from sigc::trackable in sigc++3.
466-
* If you connect the returned functor (calling %emit() on signal1) to
467-
* another signal (signal2) and then delete signal1, you must manually
465+
* @note %sigc::signal does not derive from sigc::trackable.
466+
* If you connect the returned functor that calls %emit() on signal1,
467+
* to another signal (signal2) and then delete signal1, you must manually
468468
* disconnect signal1 from signal2 before you delete signal1.
469+
* Alternatively, make a slot of a sigc::trackable_signal.
469470
*
470471
* @code
471472
* sigc::mem_fun(mysignal, &sigc::signal_with_accumulator::emit)
@@ -517,7 +518,8 @@ class signal_with_accumulator : public signal_base
517518
*
518519
* The template arguments determine the function signature of
519520
* the emit() function:
520-
* - @e T_return The desired return type of the emit() function. * - @e T_arg Argument types used in
521+
* - @e T_return The desired return type of the emit() function.
522+
* - @e T_arg Argument types used in
521523
* the definition of emit().
522524
*
523525
* For instance, to declare a signal whose connected slot returns void and takes
@@ -531,7 +533,7 @@ class signal_with_accumulator : public signal_base
531533
* @par Example:
532534
* @code
533535
* void foo(int) {}
534-
* sigc::signal<void, long> sig;
536+
* sigc::signal<void(long)> sig;
535537
* sig.connect(sigc::ptr_fun(&foo));
536538
* sig.emit(19);
537539
* @endcode
@@ -625,6 +627,283 @@ class signal<T_return(T_arg...)> : public signal_with_accumulator<T_return, void
625627
}
626628
};
627629

630+
// TODO: When we can break ABI, let signal_base derive from trackable, as in sigc++2,
631+
// and delete trackable_signal_with_accumulator and trackable_signal.
632+
// https://github.com/libsigcplusplus/libsigcplusplus/issues/80
633+
634+
/** Signal declaration.
635+
* %trackable_signal_with_accumulator can be used to connect() slots that are invoked
636+
* during subsequent calls to emit(). Any functor or slot
637+
* can be passed into connect(). It is converted into a slot implicitly.
638+
*
639+
* If you want to connect one signal to another, use make_slot()
640+
* to retrieve a functor that emits the signal when invoked.
641+
*
642+
* Be careful if you directly pass one signal into the connect()
643+
* method of another: a shallow copy of the signal is made and
644+
* the signal's slots are not disconnected until both the signal
645+
* and its clone are destroyed, which is probably not what you want!
646+
*
647+
* The following template arguments are used:
648+
* - @e T_return The desired return type for the emit() function (may be overridden by the
649+
* accumulator).
650+
* - @e T_arg Argument types used in the definition of emit().
651+
* - @e T_accumulator The accumulator type used for emission. The default
652+
* @p void means that no accumulator should be used, for example if signal
653+
* emission returns the return value of the last slot invoked.
654+
*
655+
* @newin{3,4}
656+
*
657+
* @ingroup signal
658+
*/
659+
template<typename T_return, typename T_accumulator, typename... T_arg>
660+
class trackable_signal_with_accumulator
661+
: public signal_base
662+
, public trackable
663+
{
664+
public:
665+
using slot_type = slot<T_return(T_arg...)>;
666+
667+
/** Add a slot to the list of slots.
668+
* Any functor or slot may be passed into connect().
669+
* It will be converted into a slot implicitly.
670+
* The returned connection may be stored for disconnection
671+
* of the slot at some later point. It stays valid until
672+
* the slot is disconnected from the signal.
673+
* std::function<> and C++11 lambda expressions are functors.
674+
* These are examples of functors that can be connected to a signal.
675+
*
676+
* %std::bind() creates a functor, but this functor typically has an
677+
* %operator()() which is a variadic template.
678+
* Our functor_trait can't deduce the result type
679+
* of such a functor. If you first assign the return value of %std::bind()
680+
* to a std::function, you can connect the std::function to a signal.
681+
*
682+
* @param slot_ The slot to add to the list of slots.
683+
* @return A connection.
684+
*/
685+
connection connect(const slot_type& slot_)
686+
{
687+
auto iter = signal_base::connect(slot_);
688+
auto& slot_base = *iter;
689+
return connection(slot_base);
690+
}
691+
692+
/** Add a slot to the list of slots.
693+
* @see connect(const slot_type& slot_).
694+
*/
695+
connection connect(slot_type&& slot_)
696+
{
697+
auto iter = signal_base::connect(std::move(slot_));
698+
auto& slot_base = *iter;
699+
return connection(slot_base);
700+
}
701+
702+
/** Triggers the emission of the signal.
703+
* During signal emission all slots that have been connected
704+
* to the signal are invoked unless they are manually set into
705+
* a blocking state. The parameters are passed on to the slots.
706+
* If @e T_accumulated is not @p void, an accumulator of this type
707+
* is used to process the return values of the slot invocations.
708+
* Otherwise, the return value of the last slot invoked is returned.
709+
* @param a Arguments to be passed on to the slots.
710+
* @return The accumulated return values of the slot invocations.
711+
*/
712+
decltype(auto) emit(type_trait_take_t<T_arg>... a) const
713+
{
714+
using emitter_type = internal::signal_emit<T_return, T_accumulator, T_arg...>;
715+
return emitter_type::emit(impl_, std::forward<type_trait_take_t<T_arg>>(a)...);
716+
}
717+
718+
/** Triggers the emission of the signal (see emit()). */
719+
decltype(auto) operator()(type_trait_take_t<T_arg>... a) const
720+
{
721+
return emit(std::forward<type_trait_take_t<T_arg>>(a)...);
722+
}
723+
724+
/** Creates a functor that calls emit() on this signal.
725+
*
726+
* @code
727+
* sigc::mem_fun(mysignal, &sigc::trackable_signal_with_accumulator::emit)
728+
* @endcode
729+
* yields the same result.
730+
* @return A functor that calls emit() on this signal.
731+
*/
732+
decltype(auto) make_slot() const
733+
{
734+
// TODO: Instead use std::invoke_result<> on the static emitter_type::emit()
735+
using result_type = typename internal::member_method_result<
736+
decltype(&trackable_signal_with_accumulator::emit)>::type;
737+
return bound_mem_functor<result_type (trackable_signal_with_accumulator::*)(
738+
type_trait_take_t<T_arg>...) const,
739+
type_trait_take_t<T_arg>...>(*this, &trackable_signal_with_accumulator::emit);
740+
}
741+
742+
trackable_signal_with_accumulator() = default;
743+
744+
trackable_signal_with_accumulator(const trackable_signal_with_accumulator& src)
745+
: signal_base(src), trackable(src)
746+
{
747+
}
748+
749+
trackable_signal_with_accumulator(trackable_signal_with_accumulator&& src)
750+
: signal_base(std::move(src)), trackable(std::move(src))
751+
{
752+
}
753+
754+
trackable_signal_with_accumulator& operator=(const trackable_signal_with_accumulator& src)
755+
{
756+
signal_base::operator=(src);
757+
// Don't call trackable::operator=(src).
758+
// It calls notify_callbacks(). This signal is not destroyed.
759+
return *this;
760+
}
761+
762+
trackable_signal_with_accumulator& operator=(trackable_signal_with_accumulator&& src)
763+
{
764+
signal_base::operator=(std::move(src));
765+
if (src.impl_ != impl_)
766+
src.notify_callbacks();
767+
// Don't call trackable::operator=(std::move(src)).
768+
// It calls notify_callbacks(). This signal is not destroyed.
769+
return *this;
770+
}
771+
};
772+
773+
/** %trackable_signal can be used to connect() slots that are invoked
774+
* during subsequent calls to emit(). Any functor or slot
775+
* can be passed into connect(). It is converted into a slot
776+
* implicitly.
777+
*
778+
* If you want to connect one signal to another, use make_slot()
779+
* to retrieve a functor that emits the signal when invoked.
780+
*
781+
* Be careful if you directly pass one signal into the connect()
782+
* method of another: a shallow copy of the signal is made and
783+
* the signal's slots are not disconnected until both the signal
784+
* and its clone are destroyed, which is probably not what you want!
785+
*
786+
* The template arguments determine the function signature of
787+
* the emit() function:
788+
* - @e T_return The desired return type of the emit() function.
789+
* - @e T_arg Argument types used in
790+
* the definition of emit().
791+
*
792+
* For instance, to declare a %trackable_signal whose connected slot returns void and takes
793+
* two parameters of bool and int:
794+
* @code
795+
* sigc::trackable_signal<void(bool, int)> some_signal;
796+
* @endcode
797+
*
798+
* To specify an accumulator type the nested class trackable_signal::accumulated can be used.
799+
*
800+
* @par Example:
801+
* @code
802+
* void foo(int) {}
803+
* sigc::trackable_signal<void(long)> sig;
804+
* sig.connect(sigc::ptr_fun(&foo));
805+
* sig.emit(19);
806+
* @endcode
807+
*
808+
* @newin{3,4}
809+
*
810+
* @ingroup signal
811+
*/
812+
#ifndef DOXYGEN_SHOULD_SKIP_THIS
813+
template<typename T_return, typename... T_arg>
814+
class trackable_signal;
815+
#endif // DOXYGEN_SHOULD_SKIP_THIS
816+
817+
template<typename T_return, typename... T_arg>
818+
class trackable_signal<T_return(T_arg...)>
819+
: public trackable_signal_with_accumulator<T_return, void, T_arg...>
820+
{
821+
public:
822+
using accumulator_type = void;
823+
824+
/** Like @ref sigc::trackable_signal<T_return(T_arg...)> "sigc::trackable_signal"
825+
* but the additional template parameter @e T_accumulator defines the accumulator
826+
* type that should be used.
827+
*
828+
* An accumulator is a functor that uses a pair of special iterators
829+
* to step through a list of slots and calculate a return value
830+
* from the results of the slot invocations. The iterators' operator*()
831+
* executes the slot. The return value is buffered, so that in an expression
832+
* like @code a = (*i) * (*i); @endcode the slot is executed only once.
833+
*
834+
* @par Example 1:
835+
* This accumulator calculates the arithmetic mean value:
836+
* @code
837+
* struct arithmetic_mean_accumulator
838+
* {
839+
* template<typename T_iterator>
840+
* double operator()(T_iterator first, T_iterator last) const
841+
* {
842+
* double value_ = 0;
843+
* int n_ = 0;
844+
* for (; first != last; ++first, ++n_)
845+
* value_ += *first;
846+
* return value_ / n_;
847+
* }
848+
* };
849+
* @endcode
850+
*
851+
* @par Example 2:
852+
* This accumulator stops signal emission when a slot returns zero:
853+
* @code
854+
* struct interruptable_accumulator
855+
* {
856+
* template<typename T_iterator>
857+
* bool operator()(T_iterator first, T_iterator last) const
858+
* {
859+
* for (; first != last; ++first, ++n_)
860+
* if (!*first) return false;
861+
* return true;
862+
* }
863+
* };
864+
* @endcode
865+
*
866+
* @newin{3,4}
867+
*
868+
* @ingroup signal
869+
*/
870+
template<typename T_accumulator>
871+
class accumulated : public trackable_signal_with_accumulator<T_return, T_accumulator, T_arg...>
872+
{
873+
public:
874+
accumulated() = default;
875+
accumulated(const accumulated& src)
876+
: trackable_signal_with_accumulator<T_return, T_accumulator, T_arg...>(src)
877+
{
878+
}
879+
};
880+
881+
trackable_signal() = default;
882+
883+
trackable_signal(const trackable_signal& src)
884+
: trackable_signal_with_accumulator<T_return, accumulator_type, T_arg...>(src)
885+
{
886+
}
887+
888+
trackable_signal(trackable_signal&& src)
889+
: trackable_signal_with_accumulator<T_return, accumulator_type, T_arg...>(std::move(src))
890+
{
891+
}
892+
893+
trackable_signal& operator=(const trackable_signal& src)
894+
{
895+
trackable_signal_with_accumulator<T_return, accumulator_type, T_arg...>::operator=(src);
896+
return *this;
897+
}
898+
899+
trackable_signal& operator=(trackable_signal&& src)
900+
{
901+
trackable_signal_with_accumulator<T_return, accumulator_type, T_arg...>::operator=(
902+
std::move(src));
903+
return *this;
904+
}
905+
};
906+
628907
} /* namespace sigc */
629908

630909
#endif /* SIGC_SIGNAL_H */

sigc++/signal_base.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,10 @@ struct SIGC_API signal_impl_holder
266266
* @ref sigc::signal_with_accumulator::connect() "sigc::signal::connect()".
267267
*/
268268

269-
// TODO: When we can break ABI, let signal_base derive from trackable again.
270-
// It does in sigc++2. Otherwise the slot returned from signal::make_slot()
269+
// TODO: When we can break ABI, let signal_base derive from trackable again,
270+
// as in sigc++2. Otherwise the slot returned from signal::make_slot()
271271
// is not automatically disconnected when the signal is deleted.
272+
// And delete trackable_signal_with_accumulator and trackable_signal.
272273
// https://github.com/libsigcplusplus/libsigcplusplus/issues/80
273274

274275
/** Base class for the @ref sigc::signal<T_return(T_arg...)> "sigc::signal" template.

tests/test_accumulated.cc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,11 @@ test_empty_signal()
7979
util->check_result(result_stream, "Vector result (empty slot list): empty");
8080
}
8181

82+
template<typename T_signal>
8283
void
8384
test_mean()
8485
{
85-
sigc::signal<int(int)>::accumulated<arithmetic_mean_accumulator> sig;
86+
typename T_signal::accumulated<arithmetic_mean_accumulator> sig;
8687

8788
A a;
8889
sig.connect(sigc::ptr_fun(&foo));
@@ -137,7 +138,8 @@ main(int argc, char* argv[])
137138
return util->get_result_and_delete_instance() ? EXIT_SUCCESS : EXIT_FAILURE;
138139

139140
test_empty_signal();
140-
test_mean();
141+
test_mean<sigc::signal<int(int)>>();
142+
test_mean<sigc::trackable_signal<int(int)>>();
141143
test_vector_accumulator();
142144

143145
return util->get_result_and_delete_instance() ? EXIT_SUCCESS : EXIT_FAILURE;

0 commit comments

Comments
 (0)