Coverage Report

Created: 2026-06-08 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
52.7M
        ~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, failed_ = false, alwaysForward_;
132
      private:
133
        bool updating_ = false;
134
        class UpdateChecker {  // NOLINT(cppcoreguidelines-special-member-functions)
135
            LazyObject* subject_;
136
          public:
137
942k
            explicit UpdateChecker(LazyObject* subject) : subject_(subject) {
138
942k
                subject_->updating_ = true;
139
942k
            }
140
942k
            ~UpdateChecker() {
141
942k
                subject_->updating_ = false;
142
942k
            }
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
52.7M
        bool forwardsAllNotifications() const {
174
52.7M
            return forwardsAllNotifications_;
175
52.7M
        }
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
52.7M
    : alwaysForward_(LazyObject::Defaults::instance().forwardsAllNotifications()) {}
189
190
942k
    inline void LazyObject::update() {
191
942k
        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
942k
        UpdateChecker checker(this);
204
205
        // forwards notifications only the first time
206
942k
        if (calculated_ || failed_ || 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
0
            failed_ = false;
213
            // observers don't expect notifications from frozen objects
214
0
            if (!frozen_)
215
0
                notifyObservers();
216
                // exiting notifyObservers() calculated_ could be
217
                // already true because of non-lazy observers
218
0
        }
219
942k
    }
220
221
0
    inline void LazyObject::recalculate() {
222
0
        bool wasFrozen = frozen_;
223
0
        calculated_ = frozen_ = failed_ = false;
224
0
        try {
225
0
            calculate();
226
0
        } catch (...) {
227
0
            frozen_ = wasFrozen;
228
0
            notifyObservers();
229
0
            throw;
230
0
        }
231
0
        frozen_ = wasFrozen;
232
0
        notifyObservers();
233
0
    }
234
235
0
    inline void LazyObject::freeze() {
236
0
        frozen_ = true;
237
0
    }
238
239
0
    inline void LazyObject::unfreeze() {
240
0
        // send notifications, just in case we lost any,
241
0
        // but only once, i.e. if it was frozen
242
0
        if (frozen_) {
243
0
            frozen_ = false;
244
0
            notifyObservers();
245
0
        }
246
0
    }
247
248
0
    inline void LazyObject::forwardFirstNotificationOnly() {
249
0
        alwaysForward_ = false;
250
0
    }
251
252
0
    inline void LazyObject::alwaysForwardNotifications() {
253
0
        alwaysForward_ = true;
254
0
    }
255
256
37.9M
    inline void LazyObject::calculate() const {
257
37.9M
        if (!calculated_ && !frozen_) {
258
26.1M
            calculated_ = true;   // prevent infinite recursion in
259
                                  // case of bootstrapping
260
26.1M
            try {
261
26.1M
                performCalculations();
262
26.1M
                failed_ = false;  // needed when calculate() is called
263
                                  // directly after a prior failure
264
26.1M
            } catch (...) {
265
204
                calculated_ = false;
266
204
                failed_ = true;
267
204
                throw;
268
204
            }
269
26.1M
        }
270
37.9M
    }
271
272
0
    inline bool LazyObject::isCalculated() const {
273
0
        return calculated_;
274
0
    }
275
276
0
    inline void LazyObject::setCalculated(const bool c) const {
277
0
        calculated_ = c;
278
0
    }
279
}
280
281
#endif