/src/quantlib/ql/patterns/lazyobject.hpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2003 RiskMap srl |
5 | | |
6 | | This file is part of QuantLib, a free-software/open-source library |
7 | | for financial quantitative analysts and developers - http://quantlib.org/ |
8 | | |
9 | | QuantLib is free software: you can redistribute it and/or modify it |
10 | | under the terms of the QuantLib license. You should have received a |
11 | | copy of the license along with this program; if not, please email |
12 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
13 | | <https://www.quantlib.org/license.shtml>. |
14 | | |
15 | | This program is distributed in the hope that it will be useful, but WITHOUT |
16 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
17 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
18 | | */ |
19 | | |
20 | | /*! \file lazyobject.hpp |
21 | | \brief framework for calculation on demand and result caching |
22 | | */ |
23 | | |
24 | | #ifndef quantlib_lazy_object_h |
25 | | #define quantlib_lazy_object_h |
26 | | |
27 | | #include <ql/patterns/observable.hpp> |
28 | | #include <ql/shared_ptr.hpp> |
29 | | |
30 | | namespace QuantLib { |
31 | | |
32 | | //! Framework for calculation on demand and result caching. |
33 | | /*! \ingroup patterns */ |
34 | | class LazyObject : public virtual Observable, |
35 | | public virtual Observer { |
36 | | public: |
37 | | LazyObject(); |
38 | 50.8M | ~LazyObject() override = default; |
39 | | //! \name Observer interface |
40 | | //@{ |
41 | | void update() override; |
42 | | //@} |
43 | | /*! Returns true if the instrument is calculated */ |
44 | | bool isCalculated() const; |
45 | | /*! Set calculated status */ |
46 | | void setCalculated(bool c) const; |
47 | | /*! \name Calculations |
48 | | These methods do not modify the structure of the object |
49 | | and are therefore declared as <tt>const</tt>. Data members |
50 | | which will be calculated on demand need to be declared as |
51 | | mutable. |
52 | | */ |
53 | | //@{ |
54 | | /*! This method force the recalculation of any results which |
55 | | would otherwise be cached. It is not declared as |
56 | | <tt>const</tt> since it needs to call the |
57 | | non-<tt>const</tt> <i><b>notifyObservers</b></i> method. |
58 | | |
59 | | \note Explicit invocation of this method is <b>not</b> |
60 | | necessary if the object registered itself as |
61 | | observer with the structures on which such results |
62 | | depend. It is strongly advised to follow this |
63 | | policy when possible. |
64 | | */ |
65 | | void recalculate(); |
66 | | /*! This method constrains the object to return the presently |
67 | | cached results on successive invocations, even if |
68 | | arguments upon which they depend should change. |
69 | | */ |
70 | | void freeze(); |
71 | | /*! This method reverts the effect of the <i><b>freeze</b></i> |
72 | | method, thus re-enabling recalculations. |
73 | | */ |
74 | | void unfreeze(); |
75 | | |
76 | | protected: |
77 | | /*! This method performs all needed calculations by calling |
78 | | the <i><b>performCalculations</b></i> method. |
79 | | |
80 | | \warning Objects cache the results of the previous |
81 | | calculation. Such results will be returned upon |
82 | | later invocations of |
83 | | <i><b>calculate</b></i>. When the results depend |
84 | | on arguments which could change between |
85 | | invocations, the lazy object must register itself |
86 | | as observer of such objects for the calculations |
87 | | to be performed again when they change. |
88 | | |
89 | | \warning Should this method be redefined in derived |
90 | | classes, LazyObject::calculate() should be called |
91 | | in the overriding method. |
92 | | */ |
93 | | virtual void calculate() const; |
94 | | /*! This method must implement any calculations which must be |
95 | | (re)done in order to calculate the desired results. |
96 | | */ |
97 | | virtual void performCalculations() const = 0; |
98 | | //@} |
99 | | |
100 | | public: |
101 | | //! \name Notification settings |
102 | | //@{ |
103 | | /*! This method causes the object to forward the first notification received, |
104 | | and discard the others until recalculated; the rationale is that observers |
105 | | were already notified, and don't need further notifications until they |
106 | | recalculate, at which point this object would be recalculated too. |
107 | | After recalculation, this object would again forward the first notification |
108 | | received. |
109 | | |
110 | | Although not always correct, this behavior is a lot faster |
111 | | and thus is the current default. The default can be |
112 | | changed at compile time, or at at run time by calling |
113 | | `LazyObject::Defaults::instance().alwaysForwardNotifications()`; |
114 | | the run-time change won't affect lazy objects already created. |
115 | | */ |
116 | | void forwardFirstNotificationOnly(); |
117 | | |
118 | | /*! This method causes the object to forward all notifications received. |
119 | | |
120 | | Although safer, this behavior is a lot slower and thus |
121 | | usually not the default. The default can be changed at |
122 | | compile time, or at run-time by calling |
123 | | `LazyObject::Defaults::instance().alwaysForwardNotifications()`; |
124 | | the run-time change won't affect lazy objects already |
125 | | created. |
126 | | */ |
127 | | void alwaysForwardNotifications(); |
128 | | //@} |
129 | | |
130 | | protected: |
131 | | mutable bool calculated_ = false, frozen_ = false, alwaysForward_; |
132 | | private: |
133 | | bool updating_ = false; |
134 | | class UpdateChecker { // NOLINT(cppcoreguidelines-special-member-functions) |
135 | | LazyObject* subject_; |
136 | | public: |
137 | 771k | explicit UpdateChecker(LazyObject* subject) : subject_(subject) { |
138 | 771k | subject_->updating_ = true; |
139 | 771k | } |
140 | 771k | ~UpdateChecker() { |
141 | 771k | subject_->updating_ = false; |
142 | 771k | } |
143 | | }; |
144 | | public: |
145 | | class Defaults; |
146 | | }; |
147 | | |
148 | | //! Per-session settings for the LazyObject class |
149 | | class LazyObject::Defaults : public Singleton<LazyObject::Defaults> { |
150 | | friend class Singleton<LazyObject::Defaults>; |
151 | | private: |
152 | | Defaults() = default; |
153 | | |
154 | | public: |
155 | | /*! by default, lazy objects created after calling this method |
156 | | will only forward the first notification after successful |
157 | | recalculation; see |
158 | | LazyObject::forwardFirstNotificationOnly for details. |
159 | | */ |
160 | 0 | void forwardFirstNotificationOnly() { |
161 | 0 | forwardsAllNotifications_ = false; |
162 | 0 | } |
163 | | |
164 | | /*! by default, lazy objects created after calling this method |
165 | | will always forward notifications; see |
166 | | LazyObject::alwaysForwardNotifications for details. |
167 | | */ |
168 | 0 | void alwaysForwardNotifications() { |
169 | 0 | forwardsAllNotifications_ = true; |
170 | 0 | } |
171 | | |
172 | | //! returns the current default |
173 | 50.8M | bool forwardsAllNotifications() const { |
174 | 50.8M | return forwardsAllNotifications_; |
175 | 50.8M | } |
176 | | |
177 | | private: |
178 | | #ifdef QL_FASTER_LAZY_OBJECTS |
179 | | bool forwardsAllNotifications_ = false; |
180 | | #else |
181 | | bool forwardsAllNotifications_ = true; |
182 | | #endif |
183 | | }; |
184 | | |
185 | | // inline definitions |
186 | | |
187 | | inline LazyObject::LazyObject() |
188 | 50.8M | : alwaysForward_(LazyObject::Defaults::instance().forwardsAllNotifications()) {} |
189 | | |
190 | 771k | inline void LazyObject::update() { |
191 | 771k | if (updating_) { |
192 | | #ifdef QL_THROW_IN_CYCLES |
193 | | QL_FAIL("recursive notification loop detected; you probably created an object cycle"); |
194 | | #else |
195 | 0 | return; |
196 | 0 | #endif |
197 | 0 | } |
198 | | |
199 | | // This sets updating to true (so the above check breaks the |
200 | | // infinite loop if we enter this method recursively) and will |
201 | | // set it back to false when we exit this scope, either |
202 | | // successfully or because of an exception. |
203 | 771k | UpdateChecker checker(this); |
204 | | |
205 | | // forwards notifications only the first time |
206 | 771k | if (calculated_ || alwaysForward_) { |
207 | | // set to false early |
208 | | // 1) to prevent infinite recursion |
209 | | // 2) otherways non-lazy observers would be served obsolete |
210 | | // data because of calculated_ being still true |
211 | 0 | calculated_ = false; |
212 | | // observers don't expect notifications from frozen objects |
213 | 0 | if (!frozen_) |
214 | 0 | notifyObservers(); |
215 | | // exiting notifyObservers() calculated_ could be |
216 | | // already true because of non-lazy observers |
217 | 0 | } |
218 | 771k | } |
219 | | |
220 | 0 | inline void LazyObject::recalculate() { |
221 | 0 | bool wasFrozen = frozen_; |
222 | 0 | calculated_ = frozen_ = false; |
223 | 0 | try { |
224 | 0 | calculate(); |
225 | 0 | } catch (...) { |
226 | 0 | frozen_ = wasFrozen; |
227 | 0 | notifyObservers(); |
228 | 0 | throw; |
229 | 0 | } |
230 | 0 | frozen_ = wasFrozen; |
231 | 0 | notifyObservers(); |
232 | 0 | } |
233 | | |
234 | 0 | inline void LazyObject::freeze() { |
235 | 0 | frozen_ = true; |
236 | 0 | } |
237 | | |
238 | 0 | inline void LazyObject::unfreeze() { |
239 | 0 | // send notifications, just in case we lost any, |
240 | 0 | // but only once, i.e. if it was frozen |
241 | 0 | if (frozen_) { |
242 | 0 | frozen_ = false; |
243 | 0 | notifyObservers(); |
244 | 0 | } |
245 | 0 | } |
246 | | |
247 | 0 | inline void LazyObject::forwardFirstNotificationOnly() { |
248 | 0 | alwaysForward_ = false; |
249 | 0 | } |
250 | | |
251 | 0 | inline void LazyObject::alwaysForwardNotifications() { |
252 | 0 | alwaysForward_ = true; |
253 | 0 | } |
254 | | |
255 | 25.1M | inline void LazyObject::calculate() const { |
256 | 25.1M | if (!calculated_ && !frozen_) { |
257 | 25.1M | calculated_ = true; // prevent infinite recursion in |
258 | | // case of bootstrapping |
259 | 25.1M | try { |
260 | 25.1M | performCalculations(); |
261 | 25.1M | } catch (...) { |
262 | 0 | calculated_ = false; |
263 | 0 | throw; |
264 | 0 | } |
265 | 25.1M | } |
266 | 25.1M | } |
267 | | |
268 | 0 | inline bool LazyObject::isCalculated() const { |
269 | 0 | return calculated_; |
270 | 0 | } |
271 | | |
272 | 0 | inline void LazyObject::setCalculated(const bool c) const { |
273 | 0 | calculated_ = c; |
274 | 0 | } |
275 | | } |
276 | | |
277 | | #endif |