-
Notifications
You must be signed in to change notification settings - Fork 3
/
scope_guards.qbk
991 lines (849 loc) · 33.8 KB
/
scope_guards.qbk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
[/
/ Copyright 2023 Andrey Semashev
/
/ Distributed under the Boost Software License, Version 1.0.
/ (See accompanying file LICENSE_1_0.txt or copy at
/ https://www.boost.org/LICENSE_1_0.txt)
/
/ This document is a part of Boost.Scope library documentation.
/]
[section:scope_guards Scope guards]
A scope guard is an object that invokes an arbitrary /action/ function object on destruction. Scope guards are useful for implementing
actions that need to be reliably performed upon control leaving an execution scope (for example, when returning from a function), which
is especially helpful for handling exceptions.
The wrapped action function object is specified on the scope guard construction and cannot be changed afterwards. It must be one of:
* a user-defined class with a public `operator()` taking no arguments, or
* an lvalue reference to such class, or
* an lvalue reference or pointer to a function taking no arguments.
Note that if the wrapped function is a reference to a function object, that object must be stored externally to the scope guard and
must remain valid for the entire lifetime of the scope guard.
Some scope guards also support specifying an additional /condition/ function object, which allows for customizing the conditions in
which the action function object must be called. Condition function objects are discussed in more detail in a
[link scope.scope_guards.condition_functions later section].
Boost.Scope provides four kinds of scope guards, differing in their features and conditions upon which the action function object is
called, summarised in the table below.
[table Scope guard comparison
[[Feature] [[class_scope_scope_exit]] [[class_scope_scope_success]] [[class_scope_scope_fail]] [[class_scope_scope_final]]]
[[Supports a condition function?] [Yes] [Yes] [Yes] [No]]
[[Invokes action on normal scope exit?] [Yes, by default. Depends on condition.] [Yes] [No] [Yes]]
[[Invokes action on scope exit due to failure?] [Yes, by default. Depends on condition.] [No] [Yes] [Yes]]
[[Action may throw?] [Yes, by default. Depends on condition.] [Typically yes] [Typically no] [Yes]]
[[Can be (de)activated?] [Yes] [Yes] [Yes] [No]]
[[Move-constructible? (requires function objects to be move-constructible)] [Yes] [Yes] [Yes] [No]]
[[Has factory function? (C++11-friendly)] [Yes] [Yes] [Yes] [No]]
]
In the table above, the term "failure" is used broadly. What constitutes a failure in a given context is specified by user in
the form of the condition function object. Most often, a thrown exception is taken as an indication of a failure, but it can be
changed to, for example, a check for an error code that is being returned from the enclosing function.
For [class_scope_scope_exit], there is no notion of "success" or "failure". Rather, the scope guard invokes the action function
object if the condition returns `true`. By default, if the condition function object is not specified by user, the scope
guard operates as if the condition always returns `true`, which means, for example, that it will invoke the action whether
the control leaves the scope normally or due to an exception.
Although it is possible to specify arbitrary condition function objects, the intended use case for [class_scope_scope_success]
is to invoke its action when the scope is left normally (i.e. not because of a failure) and for [class_scope_scope_fail] - to
invoke its action to handle errors, including exceptions. For this reason, action functions that are used with [class_scope_scope_fail]
are typically not allowed to throw, as this may cause the program to terminate. This is also a concern with other scope guards,
such as [class_scope_scope_exit] and [class_scope_scope_final], which also may invoke their actions due to an exception. It is
user's responsibility to ensure that scope guard actions don't throw if another exception is being propagated. Generally, it is
recommended to use scope guards to implement actions that cannot throw and move all operations that may fail to the normal code
flow.
[section:conditional Conditional scope guards: `scope_exit`, `scope_success` and `scope_fail`]
#include <``[boost_scope_scope_exit_hpp]``>
#include <``[boost_scope_scope_success_hpp]``>
#include <``[boost_scope_scope_fail_hpp]``>
The [class_scope_scope_exit], [class_scope_scope_success], and [class_scope_scope_fail] scope guards have a lot of similarities
in interfaces and capabilities and differ in conditions when they invoke the action function object. As shown in the table above,
these scope guards support specifying an additional condition function object. By default, [class_scope_scope_success] will
invoke its action if it is destroyed normally, [class_scope_scope_fail] - if it is destroyed due to an exception being thrown.
[class_scope_scope_exit] by default operates as if the condition always allows executing the action.
[tip Condition function objects will be discussed in more detail in the [link scope.scope_guards.condition_functions next] section.
Note that there are caveats with detecting whether an exception has been thrown, also discussed in the next section.]
Aside from condition function objects, each of the scope guards supports active and inactive state. The action function object will
only be called if the scope guard is in active state while being destroyed. By default, scope guards are created in active state,
but this can be changed by passing `false` as the last argument for the constructor. Scope guards can also be deactivated or
re-activated during their lifetime, which can be useful if the scope guard needs to be activated based on some run time condition
after it is created.
class collection
{
std::set< std::shared_ptr< object > > objects;
public:
void add_object(std::shared_ptr< object > const& obj)
{
// Create a deactivated scope guard initially
std::set< std::shared_ptr< object > >::iterator it;
boost::scope::scope_fail rollback_guard{[&, this]
{
objects.erase(it);
},
false};
bool inserted;
std::tie(it, inserted) = objects.insert(obj);
// Activate rollback guard, if needed
rollback_guard.set_active(inserted);
obj->on_added_to_collection(*this);
}
};
The code sample above relies on C++17 [@https://en.cppreference.com/w/cpp/language/class_template_argument_deduction class template
argument deduction (CTAD)] for `scope_fail` to deduce the function object type (which is the lambda). If this feature is not available,
the scope guard construction can be rewritten using a factory function, like this:
auto rollback_guard = boost::scope::make_scope_fail([&, this]
{
objects.erase(it);
},
false);
Factory functions are provided for each of the three scope guards described in this section and are compatible with C++11. The factory
functions are named as `make_<scope_guard>` and accept the same arguments as the corresponding scope guard's constructor.
Scope guards described in this section are move-constructible (but not assignable), which requires the wrapped function objects to be
move- or copy-constructible as well. After moving, the moved-from scope guard becomes inactive. If a moved-from scope guard is active
on destruction, the behavior is undefined.
[endsect]
[section:condition_functions Scope guard condition functions]
#include <``[boost_scope_exception_checker_hpp]``>
#include <``[boost_scope_error_code_checker_hpp]``>
As discussed before, [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail] support specifying an additional
condition function object that will be called to decide whether the scope guard should invoke the action. A condition function object must
satisfy the following requirements:
* a condition function object must be one of:
* a user-defined class with a public `operator()` taking no arguments, or
* an lvalue reference to such class, or
* an lvalue reference or pointer to a function taking no arguments;
* invoking the condition function object must return a value contextually convertible to `bool`, and
* invoking the condition function object must not throw exceptions.
For all the three scope guards, the condition function object is optional. If not specified, [class_scope_scope_exit] operates as if the
condition function object always returns `true` and will invoke its action function object as long as the scope guard is active. Otherwise,
the scope guard only invokes its action if the condition returns `true`.
For [class_scope_scope_success] and [class_scope_scope_fail], the [class_scope_exception_checker] condition function is used by default. It
works by capturing the number of uncaught exceptions on construction (which happens at the point of construction of the scope guard) and then
comparing the captured value with the number of uncaught exceptions when it is called (which happens when the scope guard is destroyed).
If the number has increased, it is taken as a sign that a new exception is in flight, and the predicate returns `true`; otherwise, the
predicate returns `false`.
[note By design, [class_scope_exception_checker] is intended for a specific use case with scope guards created on the stack. It is incompatible
with C++20 coroutines and similar facilities (e.g. fibers and userspace context switching), where the thread of execution may be suspended after
the predicate captures the number of uncaught exceptions and then resumed in a different context, where the number of uncaught exceptions
has changed. Similarly, it is incompatible with usage patterns where the predicate is cached after construction and is invoked after the
thread has left the scope where the predicate was constructed (e.g. when the predicate or the associated scope guard is stored as a class data
member or a namespace-scope variable).]
[class_scope_scope_success] invokes its action when the condition returns `false` (i.e. when the failure is not detected) and
[class_scope_scope_fail] - when the condition returns `true` (i.e. when the failure is detected).
You may notice that [class_scope_scope_exit] behavior (with a user-specified condition function object) is similar to [class_scope_scope_fail]
and opposite to [class_scope_scope_success]. The main difference is a semantic one: [class_scope_scope_exit] does not have a success/failure
connotation and may be used with arbitrary condition functions. On the other hand, [class_scope_scope_success] and [class_scope_scope_fail]
correspond to their respective intended use cases, and the default condition function makes them equivalent to the scope guards defined in
[@https://cplusplus.github.io/fundamentals-ts/v3.html#scope.syn `<experimental/scope>`].
It is possible to emulate each of the scope guards described in this section by using [class_scope_scope_exit], either with a custom condition
function object, with the condition function embedded into the action function, or by activating or deactivating the scope guard after
construction. For example, the following four pieces of code have the same effect:
[table Comparison of `scope_fail` and `scope_exit`
[[`scope_fail`][`scope_exit` with a condition][`scope_exit` with embedded condition][`scope_exit` with manual deactivation]]
[[
```
void push_back(int x, std::vector<int>& vec1,
std::vector<int>& vec2)
{
vec1.push_back(x);
// Revert vec1 modification on failure
boost::scope::scope_fail rollback_guard{[&]
{
vec1.pop_back();
}};
vec2.push_back(x);
}
```
]
[
```
void push_back(int x, std::vector<int>& vec1,
std::vector<int>& vec2)
{
vec1.push_back(x);
// Revert vec1 modification on failure
boost::scope::scope_exit rollback_guard
{
[&] { vec1.pop_back(); },
boost::scope::exception_checker()
};
vec2.push_back(x);
}
```
]
[
```
void push_back(int x, std::vector<int>& vec1,
std::vector<int>& vec2)
{
vec1.push_back(x);
// Revert vec1 modification on failure
boost::scope::scope_exit rollback_guard
{
[&, uncaught_count = std::uncaught_exceptions()]
{
if (std::uncaught_exceptions() > uncaught_count)
vec1.pop_back();
}
};
vec2.push_back(x);
}
```
]
[
```
void push_back(int x, std::vector<int>& vec1,
std::vector<int>& vec2)
{
vec1.push_back(x);
// Revert vec1 modification on failure
boost::scope::scope_exit rollback_guard{[&]
{
vec1.pop_back();
}};
vec2.push_back(x);
// Commit vec1 modification
rollback_guard.set_active(false);
}
```
]]
]
The main benefit of using [class_scope_scope_fail] in this example is reducing code size and complexity. You can see that adding the check
for the number of uncaught exceptions to the scope guard action makes it notably more verbose, and if such a check needs to be performed
in multiple scope guards, this code would have to be duplicated. Explicitly deactivating the scope guard also has its downsides, as it may
be prone to errors if it has to be performed in multiple places of the code (for example, when there are multiple return points, where
the transaction needs to be committed). This goes against the intended purpose of the scope guard, as it was supposed to automate the correct
execution of its action without the user having to manually ensure this at every possible point of leaving the scope.
Another benefit of [class_scope_scope_fail] is improved code readability. For a reader, [class_scope_scope_fail] immediately indicates
an error handler, whereas [class_scope_scope_exit] does not have such connotation and may contain a general cleanup code.
That being said, there may be reasons to still use one of the techniques demonstrated above instead of
[class_scope_scope_success]/[class_scope_scope_fail], especially when paired with [class_scope_exception_checker]. As noted above,
[class_scope_exception_checker] is incompatible with coroutines and similar facilities, so using a manually managed [class_scope_scope_exit]
can be a solution. Another consideration is performance, specifically in relation with [class_scope_exception_checker]. This predicate has
to invoke runtime functions to obtain the number of uncaught exceptions, and has to do this twice - on scope guard construction and destruction.
Although these functions are usually cheap, these calls are typically not optimized away by the compiler, even if no exception is thrown,
and deactivating a scope guard is cheaper still. So, if a scope guard is used in a tight loop where its performance overhead may be significant,
preferring [class_scope_scope_exit] with manual deactivation may be a reasonable choice.
[class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail] accept the condition function object as the second argument
for the constructor or factory function, after the action function object. If the condition function object is default-constructible and
the default constructor doesn't throw, the function object may be omitted from the scope guard constructor arguments.
// Writes a string to a file using a file descriptor, with file locking.
// Reports errors either via an error_code parameter.
void locked_write_string(int fd, std::string const& str, std::error_code& ec)
{
int err = 0;
// Automatically transform errors to error_code on exit
boost::scope::scope_fail fail_guard
{
// Action function object
[&] { ec = std::error_code(err, std::generic_category()); },
// Condition function object
[&err]() noexcept { return err != 0 && err != EINTR; }
};
// Lock the file
while (flock(fd, LOCK_EX) < 0)
{
err = errno;
if (err != EINTR)
return;
}
// Unlock the file on exit
BOOST_SCOPE_FINAL [&]
{
while (flock(fd, LOCK_UN) < 0)
{
err = errno;
if (err != EINTR)
return;
}
};
// Write data
const char* p = str.data();
std::size_t size_left = str.size();
while (size_left > 0)
{
ssize_t written = write(fd, p, size_left);
if (written < 0)
{
err = errno;
if (err == EINTR)
continue;
return;
}
p += written;
size_left -= written;
}
}
Besides [class_scope_exception_checker], the library also provides [class_scope_error_code_checker] that can be used as a condition
function object. On construction, [class_scope_error_code_checker] captures a reference to an external error code object and checks it
for an error indication when being called. This implies that the error code object must remain valid for the entire lifetime duration of
the [class_scope_error_code_checker] predicate.
An object `ec` can be used as an error code object if:
* the expression `!ec` is valid, never throws, and returns a value contextually convertible to `bool`, and
* the expression `!ec` produces a value contextually convertible to `true` when there is no error and `false` otherwise.
That is, for an error code object `ec`, invoking [class_scope_error_code_checker] results in a value equivalent to `!!ec`. This makes
[class_scope_error_code_checker] compatible with a wide variety of error code types, including:
* `std::error_code` or `boost::system::error_code` from __boost_system__,
* `std::expected`, `boost::outcome_v2::basic_outcome` or `boost::outcome_v2::basic_result` from __boost_outcome__,
* `int`, where the value of 0 indicates no error,
* `bool`, where the value of `false` indicates no error,
* `T*`, where a null pointer indicates no error.
For C++11 compilers, the library also provides a factory function `check_error_code`. For example, our previous example could use
this condition function object like this:
// Writes a string to a file using a file descriptor, with file locking.
// Reports errors either via an error_code parameter.
void locked_write_string(int fd, std::string const& str, std::error_code& ec)
{
int err = 0;
// Automatically transform errors to error_code on exit
boost::scope::scope_fail fail_guard
{
// Action function object
[&]
{
if (err != EINTR)
ec = std::error_code(err, std::generic_category());
},
// Condition function object
boost::scope::check_error_code(err)
};
// ...
}
Note that we had to move the test for `EINTR` to the scope guard action since [class_scope_error_code_checker] would only test `err`
for being zero.
[endsect]
[section:unconditional Unconditional scope guard: `scope_final`]
#include <``[boost_scope_scope_final_hpp]``>
The [class_scope_scope_final] scope guard is similar to [class_scope_scope_exit] without a user-specified condition function object in
terms of when it invokes the action function. But it does not support custom condition functions and lacks support for moveability and
activation/deactivation - this scope guard is always active upon construction. This allows for a more efficient implementation when
these features are not needed.
[note [class_scope_scope_final] is a more lightweight version of [class_scope_scope_exit], similar to how `std::lock_guard` is a more
lightweight version of `std::unique_lock`.]
Since [class_scope_scope_final] effectively provides no interface to interact with after construction, it is better suited for anonymous
"set up and forget" kind of scope guards. To emphasize this affinity, the library provides a `BOOST_SCOPE_FINAL` macro, which acts as
a keyword defining a uniquely named [class_scope_scope_final] scope guard. The macro should be followed by the function object to be
invoked on scope exit.
BOOST_SCOPE_FINAL []
{
std::cout << "Hello world!" << std::endl;
};
[note `BOOST_SCOPE_FINAL` requires support for C++17 [@https://en.cppreference.com/w/cpp/language/class_template_argument_deduction CTAD].
The [class_scope_scope_final] class itself is compatible with C++11, but given that there is no factory function for it, C++17 support is
very much desired.]
As you can see, `BOOST_SCOPE_FINAL` offers a few syntax improvements over the other scope guard declarations:
* The declaration does not name a scope guard variable, meaning one does not need to invent one and there is no possibility to accidentally
omit one or clash with other variables.
* The declaration is generally shorter to type and easier to spot.
* There are no extra parenthesis or curly brackets around the function object.
However, it should be noted that the use of the `BOOST_SCOPE_FINAL` macro is entirely optional. Users are free to use [class_scope_scope_final]
directly.
boost::scope::scope_final guard{[]
{
std::cout << "Hello world!" << std::endl;
}};
[endsect]
[section:runtime_defined Setting up scope exit actions at run time]
It is possible to use scope guard classes to implement scope exit actions that are initialized at run time. To implement this, one could use
a function object wrapper such as `std::function` together with the scope guard to schedule the function call. For example:
using cleanup_func_t = std::function< void() >;
// Create an inactive scope guard first, since the cleanup function is not set yet
boost::scope::scope_exit< cleanup_func_t > cleanup(cleanup_func_t(), false);
// Later in the program, initialize the scope guard with the function selected at run time
if (cond)
{
cleanup = boost::scope::scope_exit< cleanup_func_t >([]
{
std::cout << "cond is true" << std::endl;
});
}
else
{
cleanup = boost::scope::scope_exit< cleanup_func_t >([]
{
std::cout << "cond is false" << std::endl;
});
}
It is also possible to do this with `BOOST_SCOPE_FINAL`, although it eliminates one of the advantages provided by this macro, namely not
having to invent a variable name. Also note that the function wrapper must be valid at all times once the scope guard is constructed.
// Create a non-empty function wrapper that does nothing
std::function< void() > cleanup_func = [] {};
// Create a scope guard that refers to the function wrapper
BOOST_SCOPE_FINAL std::ref(cleanup_func);
// Later in the program, initialize the function wrapper
if (cond)
{
cleanup_func = []
{
std::cout << "cond is true" << std::endl;
};
}
else
{
cleanup_func = []
{
std::cout << "cond is false" << std::endl;
};
}
However, when setting up scope exit actions at run time like that, users should be aware that function wrappers typically use dynamic
memory allocation internally and copy the function object data, which may involve calling copy constructors that may also fail with an
exception. Although many standard library implementations use small object optimization for `std::function`, and this technique is also
used in other implementations like __boost_function__, it is generally not guaranteed that initializing the function wrapper with a given
function object will not throw. If setting up the scope exit action needs to be a non-throwing operation (for example, if the scope guard
is supposed to revert the effects of the immediately preceding operation), it is recommended to initialize inactive scope guards beforehand
and only activate one of them at a later point in the program.
// Create inactive scope guards for both branches
boost::scope::scope_exit cleanup_true([]
{
std::cout << "cond is true" << std::endl;
},
false);
boost::scope::scope_exit cleanup_false([]
{
std::cout << "cond is false" << std::endl;
},
false);
// Later in the program, activate one of the scope guards.
// This won't throw.
if (cond)
cleanup_true.set_active(true);
else
cleanup_false.set_active(true);
Alternatively, one could implement the selection within the scope guard action itself.
// Create a single inactive scope guard that implements both branches
bool cond;
boost::scope::scope_exit cleanup([&cond]
{
if (cond)
std::cout << "cond is true" << std::endl;
else
std::cout << "cond is false" << std::endl;
},
false);
// Later in the program, select the branch to perform
// and activate the scope guard.
cond = select_branch();
cleanup.set_active(true);
[endsect]
[section:comparison_with_boost_scope_exit Comparison with Boost.ScopeExit library]
__boost_scope_exit__ defines a set of macros for defining code blocks to be executed at scope exit. Scope guards provided by Boost.Scope
provide similar functionality, but with simpler syntax and new features. Differences between libraries are summarized in the table below.
[table __boost_scope_exit__ and Boost.Scope comparison
[[Feature] [__boost_scope_exit__] [Boost.Scope]]
[[
Minimum and recommended C++ version.
]
[
C++03 minimum, C++11 recommended for some features.
]
[
C++11 minimum, C++17 recommended for some features.
]]
[[
Basic functionality.
]
[
```
BOOST_SCOPE_EXIT(void)
{
std::cout << "Hello, World!" << std::endl;
}
BOOST_SCOPE_EXIT_END;
```
]
[
```
boost::scope::scope_exit guard([]
{
std::cout << "Hello, World!" << std::endl;
});
```
Or:
```
BOOST_SCOPE_FINAL []
{
std::cout << "Hello, World!" << std::endl;
};
```
]]
[[
Capturing variables.
]
[
Yes, both by value and by reference.
```
int x;
std::string str;
BOOST_SCOPE_EXIT(x, &str)
{
std::cout << "x = " << x << ", str = " << str << std::endl;
}
BOOST_SCOPE_EXIT_END;
```
In C++03 mode without variadic macros support, Boost.Preprocessor sequence should be used to list captured variables.
]
[
Yes, by means of the standard C++ lambda captures.
```
int x;
std::string str;
boost::scope::scope_exit guard([x, &str]
{
std::cout << "x = " << x << ", str = " << str << std::endl;
});
```
Or:
```
int x;
std::string str;
BOOST_SCOPE_FINAL [x, &str]
{
std::cout << "x = " << x << ", str = " << str << std::endl;
};
```
]]
[[
Capturing all variables.
]
[
Yes, requires C++11.
```
int x;
std::string str;
BOOST_SCOPE_EXIT_ALL(&)
{
std::cout << "x = " << x << ", str = " << str << std::endl;
};
```
Note that no `BOOST_SCOPE_EXIT_END` is used in this case. See below for `BOOST_SCOPE_EXIT_ALL` caveats.
]
[
Yes, by means of the standard C++ lambda captures.
```
int x;
std::string str;
boost::scope::scope_exit guard([&]
{
std::cout << "x = " << x << ", str = " << str << std::endl;
});
```
Or:
```
int x;
std::string str;
BOOST_SCOPE_FINAL [&]
{
std::cout << "x = " << x << ", str = " << str << std::endl;
};
```
]]
[[
Capturing `this`.
]
[
Yes, with a special `this_` keyword.
```
class my_class
{
private:
int x;
std::string str;
public:
void foo()
{
BOOST_SCOPE_EXIT(this_)
{
std::cout << "x = " << this_->x << ", str = " << this_->str << std::endl;
}
BOOST_SCOPE_EXIT_END;
}
};
```
Note that the keyword must be explicitly used to reference members of the class within the scope exit block.
Capturing `this` is also possible using `BOOST_SCOPE_EXIT_ALL` (with the caveats described below).
```
class my_class
{
private:
int x;
std::string str;
public:
void foo()
{
BOOST_SCOPE_EXIT_ALL(this)
{
std::cout << "x = " << x << ", str = " << str << std::endl;
};
}
};
```
]
[
Yes, by means of the standard C++ lambda captures.
```
class my_class
{
private:
int x;
std::string str;
public:
void foo()
{
boost::scope::scope_exit guard([this]
{
std::cout << "x = " << x << ", str = " << str << std::endl;
};
}
};
```
Or:
```
class my_class
{
private:
int x;
std::string str;
public:
void foo()
{
BOOST_SCOPE_FINAL [this]
{
std::cout << "x = " << x << ", str = " << str << std::endl;
};
}
};
```
]]
[[
Capturing specific members of a class.
]
[
Yes.
```
class my_class
{
private:
int x;
std::string str;
public:
void foo()
{
BOOST_SCOPE_EXIT(x, &str)
{
std::cout << "x = " << x << ", str = " << str << std::endl;
}
BOOST_SCOPE_EXIT_END;
}
};
```
]
[
No. Capture `this` or use lambda capture initializers instead.
```
class my_class
{
private:
int x;
std::string str;
public:
void foo()
{
BOOST_SCOPE_FINAL [x = x, str = std::ref(str)]
{
std::cout << "x = " << x << ", str = " << str.get() << std::endl;
};
}
};
```
]]
[[
Support for scope guards in templates.
]
[
Yes, using a special macro.
```
template< typename T >
void foo(T const& param)
{
BOOST_SCOPE_EXIT_TPL(¶m)
{
std::cout << "Param: " << param << std::endl;
}
BOOST_SCOPE_EXIT_END;
}
```
]
[
Yes, with no special syntax.
```
template< typename T >
void foo(T const& param)
{
BOOST_SCOPE_FINAL [¶m]
{
std::cout << "Param: " << param << std::endl;
};
}
```
]]
[[
Support for variadic templates and argument packs.
]
[
Yes, using the `BOOST_SCOPE_EXIT_ALL` macro (see the caveats below).
```
template< typename... Args >
void foo(Args&&... args)
{
BOOST_SCOPE_EXIT_ALL(&args...)
{
std::cout << "Params: " << boost::tuples::tie(args...) << std::endl;
};
}
```
]
[
Yes, by means of the standard C++ lambda captures.
```
template< typename... Args >
void foo(Args&&... args)
{
BOOST_SCOPE_FINAL [&args...]
{
std::cout << "Params: " << boost::tuples::tie(args...) << std::endl;
};
}
```
]]
[[
Native support for activation/deactivation of the scope guard.
]
[
No, the activation/deactivation logic needs to be embedded into the action code block.
```
bool active = false;
BOOST_SCOPE_EXIT(&active)
{
if (active)
std::cout << "Hello, World!" << std::endl;
}
BOOST_SCOPE_EXIT_END;
active = true;
```
]
[
Yes, with [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail]. This includes the ability to delay activation
of the initially inactive scope guards.
```
boost::scope::scope_exit guard([]
{
std::cout << "Hello, World!" << std::endl;
},
false);
guard.set_active(true);
```
]]
[[
Support for custom conditions for executing the action.
]
[
No, the condition needs to be embedded into the action code block.
```
const auto uncaught_count = std::uncaught_exceptions();
BOOST_SCOPE_EXIT(uncaught_count)
{
if (std::uncaught_exceptions() > uncaught_count)
std::cout << "Exception thrown!" << std::endl;
}
BOOST_SCOPE_EXIT_END;
```
]
[
Yes, with [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail].
```
boost::scope::scope_exit guard(
[]
{
std::cout << "Exception thrown!" << std::endl;
},
[uncaught_count = std::uncaught_exceptions()]
{
return std::uncaught_exceptions() > uncaught_count;
});
```
Or simply:
```
boost::scope::scope_fail guard([]
{
std::cout << "Exception thrown!" << std::endl;
});
```
]]
[[
Utilities for checking exceptions and error codes as criteria for invoking the action.
]
[
No.
```
std::error_code ec;
BOOST_SCOPE_EXIT(&ec)
{
if (ec)
std::cout << "Execution failed!" << std::endl;
}
BOOST_SCOPE_EXIT_END;
```
]
[
Yes, [class_scope_exception_checker] and [class_scope_error_code_checker].
```
std::error_code ec;
boost::scope::scope_fail guard([]
{
std::cout << "Execution failed!" << std::endl;
},
boost::scope::check_error_code(ec));
```
]]
[[
Named scope guards.
]
[
No, the scope guard is declared as an unnamed code block. (The scope guard is internally implemented as an object with a unique name on
the stack, but this is not part of the public interface.)
]
[
Yes, scope guards are defined as objects. Also, [class_scope_scope_exit], [class_scope_scope_success] and [class_scope_scope_fail] are
move-constructible and can be returned from functions.
]]
[[
Anonymous scope guards.
]
[
Yes.
]
[
Yes, using the `BOOST_SCOPE_FINAL` macro.
]]
[[
Macro-less usage.
]
[
No.
]
[
Yes.
]]
]
In the table above, you may notice that a significant amount of feature parity between __boost_scope_exit__ and Boost.Scope is achieved by
using the `BOOST_SCOPE_EXIT_ALL` macro in the former. This macro is implemented using C++11 lambda functions internally, which explains
a lot of similarities between the libraries in this case. Users can also define `BOOST_SCOPE_EXIT_CONFIG_USE_LAMBDAS` to force other
__boost_scope_exit__ macros to use C++11 lambdas internally, which will change the syntax and variable capture rules accordingly. However,
there are a few caveats one needs to keep in mind when using the library in this mode (or the `BOOST_SCOPE_EXIT_ALL` macro in any mode):
* Since it relies on C++11 lambda functions, it is not available in C++03 mode.
* __boost_scope_exit__ scope guards that are implemented using C++11 lambdas store the lambda function in a `boost::function` object
internally. This means that creating the scope guard may require dynamic memory allocation and can fail with an exception. __boost_scope_exit__
does not document behavior in this case, but in practice, as of Boost 1.83.0, the scope exit block does not get executed in this case.
This means that __boost_scope_exit__ in this mode cannot be safely used in non-throwing contexts, and cannot guarantee execution of the
scope exit block. There is also performance overhead associated both with construction and execution of the scope guard.
In comparison, Boost.Scope scope guards do not use function object wrappers, such as `boost::function` or `std::function`, to store the
function objects. This means that the scope guards do not perform dynamic memory allocation (unless the function objects themselves do)
and do not have the associated exception safety issues and performance overhead. Additionally, the scope guards will execute the action
if initializing the scope guard fails with an exception.
[endsect]
[endsect]