Skip to content

Commit 0f6b381

Browse files
committed
Fix out double destruction bug, added support for out chaining, closes #109
Fixed `out::construct*`` to actually forward Just unconditionally don't include `<execution>`
1 parent 4263420 commit 0f6b381

File tree

6 files changed

+184
-27
lines changed

6 files changed

+184
-27
lines changed

include/cpp2util.h

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -131,11 +131,6 @@
131131
#include <iterator>
132132
#include <ranges>
133133
#include <algorithm>
134-
// libstdc++ sometime after GCC 10 got a dependency on linking TBB if <execution> is
135-
// included, so let's not pull in that header in our "import std;" simulation mode
136-
#if not defined(__GLIBCXX__) || __GLIBCXX__ < 20210101
137-
#include <execution>
138-
#endif
139134
#include <bit>
140135
#include <cfenv>
141136
#include <cmath>
@@ -185,6 +180,15 @@
185180
#endif
186181
#include <thread>
187182
#include <iso646.h>
183+
184+
// libstdc++ currently has a dependency on linking TBB if <execution> is
185+
// included, and TBB seems to be not automatically installed and linkable
186+
// on some GCC installations, so let's not pull in that little-used header
187+
// in our -pure-cpp2 "import std;" simulation mode... if you need this,
188+
// use mixed mode (not -pure-cpp2) and #include all the headers you need
189+
// including this one
190+
//
191+
// #include <execution>
188192
#endif
189193

190194
// Otherwise, we're not in -pure-cpp2 and so just #include
@@ -395,9 +399,11 @@ class deferred_init {
395399
template<typename U>
396400
friend class out;
397401

402+
auto destroy() -> void { if (init) { t().~T(); } init = false; }
403+
398404
public:
399405
deferred_init() noexcept { }
400-
~deferred_init() noexcept { if (init) t().~T(); }
406+
~deferred_init() noexcept { destroy(); }
401407
auto value() noexcept -> T& { Default.expects(init); return t(); }
402408

403409
auto construct (auto&& ...args) -> void { Default.expects(!init); new (&data) T(std::forward<decltype(args)>(args)...); init = true; }
@@ -411,46 +417,79 @@ class out {
411417
T* t;
412418
deferred_init<T>* dt;
413419
};
414-
int uncaught_count = std::uncaught_exceptions();
420+
out<T>* ot = nullptr;
415421
bool has_t;
416-
bool called_construct = false;
422+
423+
// Each out in a chain contains its own uncaught_count ...
424+
int uncaught_count = std::uncaught_exceptions();
425+
// ... but all in a chain share the topmost called_construct_
426+
bool called_construct_ = false;
417427

418428
public:
419-
out(T* t) noexcept : t{t}, has_t{true} { }
420-
out(deferred_init<T>* dt) noexcept : dt{dt}, has_t{false} { }
429+
out(T* t) noexcept : t{ t}, has_t{true} { Default.expects( t); }
430+
out(deferred_init<T>* dt) noexcept : dt{dt}, has_t{false} { Default.expects(dt); }
431+
out(out<T>* ot) noexcept : ot{ot}, has_t{ot->has_t} { Default.expects(ot);
432+
if (has_t) { t = ot->t; }
433+
else { dt = ot->dt; }
434+
}
435+
436+
auto called_construct() -> bool& {
437+
if (ot) { return ot->called_construct(); }
438+
else { return called_construct_; }
439+
}
421440

422441
// In the case of an exception, if the parameter was uninitialized
423442
// then leave it in the same state on exit (strong guarantee)
424443
~out() {
425-
if (called_construct && uncaught_count != std::uncaught_exceptions()) {
444+
if (called_construct() && uncaught_count != std::uncaught_exceptions()) {
426445
Default.expects(!has_t);
427-
dt->value().~T();
446+
dt->destroy();
447+
called_construct() = false;
428448
}
429449
}
430450

431-
auto construct (auto ...args) -> void {
451+
auto construct(auto&& ...args) -> void {
432452
if (has_t) {
433-
*t = T(args...);
434-
}
435-
else if (dt->init) {
436-
dt->value() = T(args...);
453+
Default.expects( t );
454+
*t = T(std::forward<decltype(args)>(args)...);
437455
}
438456
else {
439-
dt->construct(args...);
440-
called_construct = true;
457+
Default.expects( dt );
458+
if (dt->init) {
459+
dt->value() = T(std::forward<decltype(args)>(args)...);
460+
}
461+
else {
462+
dt->construct(std::forward<decltype(args)>(args)...);
463+
called_construct() = true;
464+
}
441465
}
442466
}
443467

444-
auto construct_list(auto ...args) -> void {
468+
auto construct_list(auto&& ...args) -> void {
445469
if (has_t) {
446-
*t = T{args...};
470+
Default.expects( t );
471+
*t = T{std::forward<decltype(args)>(args)...};
472+
}
473+
else {
474+
Default.expects( dt );
475+
if (dt->init) {
476+
dt->value() = T{std::forward<decltype(args)>(args)...};
477+
}
478+
else {
479+
dt->construct_list(std::forward<decltype(args)>(args)...);
480+
called_construct() = true;
481+
}
447482
}
448-
else if (dt->init) {
449-
dt->value() = T{args...};
483+
}
484+
485+
auto value() noexcept -> T& {
486+
if (has_t) {
487+
Default.expects( t );
488+
return *t;
450489
}
451490
else {
452-
dt->construct_list(args...);
453-
called_construct = true;
491+
Default.expects( dt );
492+
return dt->value();
454493
}
455494
}
456495
};
@@ -860,8 +899,8 @@ class c_raii {
860899

861900
c_raii(c_raii const&) = delete;
862901
auto operator=(c_raii const&) = delete;
863-
c_raii(c_raii&& that) : t {std::move(that.t)}, dtor {that.dtor} { that.dtor = nullptr; }
864-
auto operator=(c_raii&& that) { t = std::move(that.t); dtor = that.dtor; that.dtor = nullptr; }
902+
c_raii(c_raii&& that) noexcept : t {std::move(that.t)}, dtor {that.dtor} { that.dtor = nullptr; }
903+
auto operator=(c_raii&& that) noexcept { t = std::move(that.t); dtor = that.dtor; that.dtor = nullptr; }
865904
};
866905

867906
inline auto fopen( const char* filename, const char* mode ) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#include <iostream>
2+
3+
struct X {
4+
inline static int Xnum = 0;
5+
int num;
6+
X() : num{++Xnum} { std::cout << "+X " << num << "\n"; }
7+
~X() { std::cout << "-X " << num << "\n"; }
8+
X(const X& that) : num{that.num} { std::cout << "+X copy " << num << "\n"; }
9+
void operator=(const X& that) { num = that.num; std::cout << "=X copy " << num << "\n"; }
10+
};
11+
12+
auto throw_1() { throw 1; }
13+
14+
struct C {
15+
std::string f;
16+
C(std::string const& fn) : f{fn} { std::cout << "enter " << f << "\n"; }
17+
~C() { std::cout << "exit " << f << "\n"; }
18+
};
19+
20+
//-------------------------------------------------------
21+
// 0x: Test one level of out and immediate throw
22+
f00: () = { c:C="f00"; x: X; f01(out x); }
23+
f01: (out x: X) = { c:C="f01"; x=(); throw_1(); }
24+
25+
//-------------------------------------------------------
26+
// 1x: Test multiple levels of out and intermediate throw
27+
f10: () = { c:C="f10"; x: X; f11(out x); }
28+
f11: (out x: X) = { c:C="f11"; f12(out x); }
29+
f12: (out x: X) = { c:C="f12"; f13(out x); throw_1(); }
30+
f13: (out x: X) = { c:C="f13"; f14(out x); }
31+
f14: (out x: X) = { c:C="f14"; x=(); }
32+
33+
int main() {
34+
C c("main");
35+
try { f00(); } catch (int) {}
36+
std::cout << "\n";
37+
try { f10(); } catch (int) {}
38+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// ----- Cpp2 support -----
2+
#include "cpp2util.h"
3+
4+
#line 1 "mixed-out-destruction.cpp2"
5+
#include <iostream>
6+
7+
struct X {
8+
inline static int Xnum = 0;
9+
int num;
10+
X() : num{++Xnum} { std::cout << "+X " << num << "\n"; }
11+
~X() { std::cout << "-X " << num << "\n"; }
12+
X(const X& that) : num{that.num} { std::cout << "+X copy " << num << "\n"; }
13+
void operator=(const X& that) { num = that.num; std::cout << "=X copy " << num << "\n"; }
14+
};
15+
16+
auto throw_1() { throw 1; }
17+
18+
struct C {
19+
std::string f;
20+
C(std::string const& fn) : f{fn} { std::cout << "enter " << f << "\n"; }
21+
~C() { std::cout << "exit " << f << "\n"; }
22+
};
23+
24+
#line 22 "mixed-out-destruction.cpp2"
25+
auto f00() -> void;
26+
auto f01(cpp2::out<X> x) -> void;
27+
#line 27 "mixed-out-destruction.cpp2"
28+
auto f10() -> void;
29+
auto f11(cpp2::out<X> x) -> void;
30+
auto f12(cpp2::out<X> x) -> void;
31+
auto f13(cpp2::out<X> x) -> void;
32+
auto f14(cpp2::out<X> x) -> void;
33+
34+
int main() {
35+
C c("main");
36+
try { f00(); } catch (int) {}
37+
std::cout << "\n";
38+
try { f10(); } catch (int) {}
39+
}
40+
41+
//=== Cpp2 definitions ==========================================================
42+
43+
#line 19 "mixed-out-destruction.cpp2"
44+
45+
//-------------------------------------------------------
46+
// 0x: Test one level of out and immediate throw
47+
auto f00() -> void{ C c { "f00" }; cpp2::deferred_init<X> x; f01(&x);}
48+
auto f01(cpp2::out<X> x) -> void{C c { "f01" }; x.construct();throw_1();}
49+
50+
//-------------------------------------------------------
51+
// 1x: Test multiple levels of out and intermediate throw
52+
auto f10() -> void{ C c { "f10" }; cpp2::deferred_init<X> x; f11(&x);}
53+
auto f11(cpp2::out<X> x) -> void{C c { "f11" }; f12(&x);}
54+
auto f12(cpp2::out<X> x) -> void{C c { "f12" }; f13(&x);throw_1();}
55+
auto f13(cpp2::out<X> x) -> void{C c { "f13" }; f14(&x);}
56+
auto f14(cpp2::out<X> x) -> void{C c { "f14" }; x.construct();}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mixed-out-destruction.cpp2... ok (mixed Cpp1/Cpp2, Cpp2 code passes safety checks)
2+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
enter main
2+
enter f00
3+
enter f01
4+
+X 1
5+
exit f01
6+
-X 1
7+
exit f00
8+
9+
enter f10
10+
enter f11
11+
enter f12
12+
enter f13
13+
enter f14
14+
+X 2
15+
exit f14
16+
exit f13
17+
exit f12
18+
-X 2
19+
exit f11
20+
exit f10
21+
exit main
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mixed-out-destruction.cpp

0 commit comments

Comments
 (0)