| 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 | <http://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 | ~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 | /*! \name Calculations |
| 46 | These methods do not modify the structure of the object |
| 47 | and are therefore declared as <tt>const</tt>. Data members |
| 48 | which will be calculated on demand need to be declared as |
| 49 | mutable. |
| 50 | */ |
| 51 | //@{ |
| 52 | /*! This method force the recalculation of any results which |
| 53 | would otherwise be cached. It is not declared as |
| 54 | <tt>const</tt> since it needs to call the |
| 55 | non-<tt>const</tt> <i><b>notifyObservers</b></i> method. |
| 56 | |
| 57 | \note Explicit invocation of this method is <b>not</b> |
| 58 | necessary if the object registered itself as |
| 59 | observer with the structures on which such results |
| 60 | depend. It is strongly advised to follow this |
| 61 | policy when possible. |
| 62 | */ |
| 63 | void recalculate(); |
| 64 | /*! This method constrains the object to return the presently |
| 65 | cached results on successive invocations, even if |
| 66 | arguments upon which they depend should change. |
| 67 | */ |
| 68 | void freeze(); |
| 69 | /*! This method reverts the effect of the <i><b>freeze</b></i> |
| 70 | method, thus re-enabling recalculations. |
| 71 | */ |
| 72 | void unfreeze(); |
| 73 | |
| 74 | protected: |
| 75 | /*! This method performs all needed calculations by calling |
| 76 | the <i><b>performCalculations</b></i> method. |
| 77 | |
| 78 | \warning Objects cache the results of the previous |
| 79 | calculation. Such results will be returned upon |
| 80 | later invocations of |
| 81 | <i><b>calculate</b></i>. When the results depend |
| 82 | on arguments which could change between |
| 83 | invocations, the lazy object must register itself |
| 84 | as observer of such objects for the calculations |
| 85 | to be performed again when they change. |
| 86 | |
| 87 | \warning Should this method be redefined in derived |
| 88 | classes, LazyObject::calculate() should be called |
| 89 | in the overriding method. |
| 90 | */ |
| 91 | virtual void calculate() const; |
| 92 | /*! This method must implement any calculations which must be |
| 93 | (re)done in order to calculate the desired results. |
| 94 | */ |
| 95 | virtual void performCalculations() const = 0; |
| 96 | //@} |
| 97 | |
| 98 | public: |
| 99 | //! \name Notification settings |
| 100 | //@{ |
| 101 | /*! This method causes the object to forward the first notification received, |
| 102 | and discard the others until recalculated; the rationale is that observers |
| 103 | were already notified, and don't need further notifications until they |
| 104 | recalculate, at which point this object would be recalculated too. |
| 105 | After recalculation, this object would again forward the first notification |
| 106 | received. |
| 107 | |
| 108 | Although not always correct, this behavior is a lot faster |
| 109 | and thus is the current default. The default can be |
| 110 | changed at compile time, or at at run time by calling |
| 111 | `LazyObject::Defaults::instance().alwaysForwardNotifications()`; |
| 112 | the run-time change won't affect lazy objects already created. |
| 113 | */ |
| 114 | void forwardFirstNotificationOnly(); |
| 115 | |
| 116 | /*! This method causes the object to forward all notifications received. |
| 117 | |
| 118 | Although safer, this behavior is a lot slower and thus |
| 119 | usually not the default. The default can be changed at |
| 120 | compile time, or at run-time by calling |
| 121 | `LazyObject::Defaults::instance().alwaysForwardNotifications()`; |
| 122 | the run-time change won't affect lazy objects already |
| 123 | created. |
| 124 | */ |
| 125 | void alwaysForwardNotifications(); |
| 126 | //@} |
| 127 | |
| 128 | protected: |
| 129 | mutable bool calculated_ = false, frozen_ = false, alwaysForward_; |
| 130 | private: |
| 131 | bool updating_ = false; |
| 132 | class UpdateChecker { // NOLINT(cppcoreguidelines-special-member-functions) |
| 133 | LazyObject* subject_; |
| 134 | public: |
| 135 | explicit UpdateChecker(LazyObject* subject) : subject_(subject) { |
| 136 | subject_->updating_ = true; |
| 137 | } |
| 138 | ~UpdateChecker() { |
| 139 | subject_->updating_ = false; |
| 140 | } |
| 141 | }; |
| 142 | public: |
| 143 | class Defaults; |
| 144 | }; |
| 145 | |
| 146 | //! Per-session settings for the LazyObject class |
| 147 | class LazyObject::Defaults : public Singleton<LazyObject::Defaults> { |
| 148 | friend class Singleton<LazyObject::Defaults>; |
| 149 | private: |
| 150 | Defaults() = default; |
| 151 | |
| 152 | public: |
| 153 | /*! by default, lazy objects created after calling this method |
| 154 | will only forward the first notification after successful |
| 155 | recalculation; see |
| 156 | LazyObject::forwardFirstNotificationOnly for details. |
| 157 | */ |
| 158 | void forwardFirstNotificationOnly() { |
| 159 | forwardsAllNotifications_ = false; |
| 160 | } |
| 161 | |
| 162 | /*! by default, lazy objects created after calling this method |
| 163 | will always forward notifications; see |
| 164 | LazyObject::alwaysForwardNotifications for details. |
| 165 | */ |
| 166 | void alwaysForwardNotifications() { |
| 167 | forwardsAllNotifications_ = true; |
| 168 | } |
| 169 | |
| 170 | //! returns the current default |
| 171 | bool forwardsAllNotifications() const { |
| 172 | return forwardsAllNotifications_; |
| 173 | } |
| 174 | |
| 175 | private: |
| 176 | #ifdef QL_FASTER_LAZY_OBJECTS |
| 177 | bool forwardsAllNotifications_ = false; |
| 178 | #else |
| 179 | bool forwardsAllNotifications_ = true; |
| 180 | #endif |
| 181 | }; |
| 182 | |
| 183 | // inline definitions |
| 184 | |
| 185 | inline LazyObject::LazyObject() |
| 186 | : alwaysForward_(LazyObject::Defaults::instance().forwardsAllNotifications()) {} |
| 187 | |
| 188 | inline void LazyObject::update() { |
| 189 | if (updating_) { |
| 190 | #ifdef QL_THROW_IN_CYCLES |
| 191 | QL_FAIL("recursive notification loop detected; you probably created an object cycle" ); |
| 192 | #else |
| 193 | return; |
| 194 | #endif |
| 195 | } |
| 196 | |
| 197 | // This sets updating to true (so the above check breaks the |
| 198 | // infinite loop if we enter this method recursively) and will |
| 199 | // set it back to false when we exit this scope, either |
| 200 | // successfully or because of an exception. |
| 201 | UpdateChecker checker(this); |
| 202 | |
| 203 | // forwards notifications only the first time |
| 204 | if (calculated_ || alwaysForward_) { |
| 205 | // set to false early |
| 206 | // 1) to prevent infinite recursion |
| 207 | // 2) otherways non-lazy observers would be served obsolete |
| 208 | // data because of calculated_ being still true |
| 209 | calculated_ = false; |
| 210 | // observers don't expect notifications from frozen objects |
| 211 | if (!frozen_) |
| 212 | notifyObservers(); |
| 213 | // exiting notifyObservers() calculated_ could be |
| 214 | // already true because of non-lazy observers |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | inline void LazyObject::recalculate() { |
| 219 | bool wasFrozen = frozen_; |
| 220 | calculated_ = frozen_ = false; |
| 221 | try { |
| 222 | calculate(); |
| 223 | } catch (...) { |
| 224 | frozen_ = wasFrozen; |
| 225 | notifyObservers(); |
| 226 | throw; |
| 227 | } |
| 228 | frozen_ = wasFrozen; |
| 229 | notifyObservers(); |
| 230 | } |
| 231 | |
| 232 | inline void LazyObject::freeze() { |
| 233 | frozen_ = true; |
| 234 | } |
| 235 | |
| 236 | inline void LazyObject::unfreeze() { |
| 237 | // send notifications, just in case we lost any, |
| 238 | // but only once, i.e. if it was frozen |
| 239 | if (frozen_) { |
| 240 | frozen_ = false; |
| 241 | notifyObservers(); |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | inline void LazyObject::forwardFirstNotificationOnly() { |
| 246 | alwaysForward_ = false; |
| 247 | } |
| 248 | |
| 249 | inline void LazyObject::alwaysForwardNotifications() { |
| 250 | alwaysForward_ = true; |
| 251 | } |
| 252 | |
| 253 | inline void LazyObject::calculate() const { |
| 254 | if (!calculated_ && !frozen_) { |
| 255 | calculated_ = true; // prevent infinite recursion in |
| 256 | // case of bootstrapping |
| 257 | try { |
| 258 | performCalculations(); |
| 259 | } catch (...) { |
| 260 | calculated_ = false; |
| 261 | throw; |
| 262 | } |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | inline bool LazyObject::isCalculated() const { |
| 267 | return calculated_; |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | #endif |
| 272 | |