E-MailRelay
gslot.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gslot.h
19///
20
21#ifndef G_SLOT_H
22#define G_SLOT_H
23
24#include "gdef.h"
25#include "gexception.h"
26#include "gassert.h"
27#include <functional>
28#include <memory>
29
30#ifdef emit
31// beware Qt
32#error invalid preprocessor definition of 'emit'
33#endif
34
35namespace G
36{
37 //| \namespace G::Slot
38 /// A callback mechanism that isolates event sinks from event sources.
39 ///
40 /// The slot/signal pattern has been used in several C++ libraries including
41 /// libsigc++, Qt and boost, although it is largely redudant with modern C++.
42 /// The pattern is completely unrelated to ANSI-C or POSIX signals (signal(),
43 /// sigaction(2)).
44 ///
45 /// Usage:
46 /// \code
47 /// struct Source
48 /// {
49 /// G::Slot::Signal<int> m_signal ;
50 /// void Source::raiseEvent()
51 /// {
52 /// m_signal.emit( 123 ) ;
53 /// }
54 /// } ;
55 ///
56 /// struct Sink
57 /// {
58 /// void onEvent( int n ) ;
59 /// Sink( Source & source ) : m_source(source)
60 /// {
61 /// source.m_signal.connect( G::Slot::slot(*this,&Sink::onEvent) ) ;
62 /// }
63 /// ~Sink()
64 /// {
65 /// m_source.m_signal.disconnect() ;
66 /// }
67 /// Sink( const Sink & ) = delete ;
68 /// Sink( Sink && ) noexcept { rebind() ; }
69 /// Sink & operator=( const Sink & ) = delete ;
70 /// Sink & operator=( Sink && ) noexcept { rebind() ; return *this ; }
71 /// void rebind() noexcept( check( m_source.m_signal.rebind(*this) ) ; }
72 /// Source & m_source ;
73 /// } ;
74 /// \endcode
75 ///
76 /// For comparison the equivalent modern C++ looks like this:
77 /// \code
78 /// struct Source
79 /// {
80 /// std::function<void(int)> m_signal ;
81 /// void Source::raiseEvent()
82 /// {
83 /// if( m_signal ) m_signal( 123 ) ;
84 /// }
85 /// } ;
86 ///
87 /// struct Sink
88 /// {
89 /// void onEvent( int n ) ;
90 /// Sink( Source & source ) : m_source(source)
91 /// {
92 /// throw_already_connected_if( !source.m_signal ) ;
93 /// source.m_signal = std::bind_front(&Sink::onEvent,this) ;
94 /// }
95 /// ~Sink()
96 /// {
97 /// m_source.m_signal = nullptr ;
98 /// }
99 /// Source & m_source ;
100 /// } ;
101 /// \endcode
102 ///
103 /// Slot methods should take parameters by value or const reference but
104 /// beware of emit()ing references to data members of objects that might
105 /// get deleted. Use temporaries in the emit() call if in doubt.
106 ///
107 namespace Slot
108 {
109 //| \class G::Slot::Binder
110 /// A functor class template that contains the target object pointer
111 /// and method pointer, similar to c++20 bind_front(&T::fn,tp).
112 /// These objects are hidden in the std::function data member of
113 /// the Slot class so that the Slot is not dependent on the target
114 /// type. Maybe replace with a lambda.
115 ///
116 template <typename T, typename... Args>
117 struct Binder
118 {
119 using Mf = void (T::*)(Args...) ;
120 T * m_sink ;
121 Mf m_mf ;
122 Binder( T * sink , Mf mf ) :
123 m_sink(sink) ,
124 m_mf(mf)
125 {
126 }
127 void rebind( T * sink ) noexcept
128 {
129 m_sink = sink ;
130 }
131 void operator()( Args... args )
132 {
133 return (m_sink->*m_mf)( args... ) ;
134 }
135 } ;
136
137 //| \class G::Slot::Slot
138 /// A slot class template that is parameterised only on the target method's
139 /// signature (with an implicit void return) and not on the target class.
140 /// The implementation uses std::function to hide the type of the target.
141 ///
142 template <typename... Args>
143 struct Slot
144 {
145 std::function<void(Args...)> m_fn ;
146 Slot() noexcept = default;
147 template <typename T> Slot( T & sink , void (T::*mf)(Args...) ) :
148 m_fn(std::function<void(Args...)>(Binder<T,Args...>(&sink,mf)))
149 {
150 }
151 explicit Slot( std::function<void(Args...)> fn ) :
152 m_fn(fn)
153 {
154 }
155 void invoke( Args... args )
156 {
157 if( m_fn )
158 m_fn( args... ) ;
159 }
160 template <typename T> bool rebind( T & sink ) noexcept
161 {
162 using BinderType = Binder<T,Args...> ;
163 bool rebindable = m_fn && m_fn.template target<BinderType>() ;
164 if( rebindable )
165 m_fn.template target<BinderType>()->rebind( &sink ) ;
166 return rebindable ;
167 }
168 } ;
169
170 //| \class G::Slot::SignalImp
171 /// A slot/signal scoping class.
172 ///
173 struct SignalImp
174 {
175 G_EXCEPTION_CLASS( AlreadyConnected , tx("signal already connected") )
176 SignalImp() = delete ;
177 } ;
178
179 //| \class G::Slot::Signal
180 /// A slot holder, with connect() and emit() methods.
181 ///
182 template <typename... SlotArgs>
183 struct Signal
184 {
185 Slot<SlotArgs...> m_slot ;
186 bool m_once ;
187 bool m_emitted {false} ;
188 explicit Signal( bool once = false ) :
189 m_once(once)
190 {
191 }
192 void connect( Slot<SlotArgs...> slot )
193 {
194 if( m_slot.m_fn ) throw SignalImp::AlreadyConnected() ;
195 m_slot = slot ;
196 }
197 void disconnect() noexcept
198 {
199 m_slot.m_fn = nullptr ;
200 G_ASSERT( !connected() ) ;
201 }
202 void emit( SlotArgs... args )
203 {
204 if( !m_once || !m_emitted )
205 {
206 m_emitted = true ;
207 if( connected() )
208 m_slot.invoke( args... ) ;
209 }
210 }
211 void reset() noexcept
212 {
213 m_emitted = false ;
214 }
215 bool connected() const
216 {
217 return !! m_slot.m_fn ;
218 }
219 bool emitted() const noexcept
220 {
221 return m_emitted ;
222 }
223 void emitted( bool emitted ) noexcept
224 {
225 m_emitted = emitted ;
226 }
227 template <typename T> bool rebind( T & sink ) noexcept
228 {
229 return m_slot.rebind( sink ) ;
230 }
231 ~Signal() = default ;
232 Signal( const Signal & ) = delete ;
233 Signal( Signal && ) noexcept = default ;
234 Signal & operator=( const Signal & ) = delete ;
235 Signal & operator=( Signal && ) noexcept = default ;
236 } ;
237
238 /// A factory function for Slot objects.
239 ///
240 template <typename TSink,typename... Args> Slot<Args...> slot( TSink & sink , void (TSink::*method)(Args...) )
241 {
242 // or c++20: return std::function<void(Args...)>( std::bind_front(method,&sink) )
243 return Slot<Args...>( sink , method ) ;
244 }
245 }
246}
247#endif
Slot< Args... > slot(TSink &sink, void(TSink::*method)(Args...))
A factory function for Slot objects.
Definition: gslot.h:240
Low-level classes.
Definition: garg.h:36
constexpr const char * tx(const char *p) noexcept
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
A functor class template that contains the target object pointer and method pointer,...
Definition: gslot.h:118
A slot holder, with connect() and emit() methods.
Definition: gslot.h:184
A slot class template that is parameterised only on the target method's signature (with an implicit v...
Definition: gslot.h:144