Coverage Report

Created: 2026-01-25 06:59

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
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