source: libcfa/src/raii.hfa@ d66a43b

Last change on this file since d66a43b was cfbc56ec, checked in by Michael Brooks <mlbrooks@…>, 21 months ago

Enable array RAII and provide uninit(-), a uNoCtor equivalent.

Enable construction/destruction of "new" CFA array elements,
which was previously deactivated to avoid a compiler performance issue.
The enabled RAII steps more carefully around the performance issue.

Provide uninit(-), with tests covering the typical use case:

struct Foo;
void ?{}( Foo & this, int i ) { printf( "ctor at %d\n", i ); }
uninit(Foo) a[10]; no prints
for (i; 10) (a[i]){ i };
prints
array(uninit(Foo), 10) b; no prints
for (i; 10) (b[i]){ i };
prints

  • Property mode set to 100644
File size: 4.2 KB
RevLine 
[cfbc56ec]1//
2// Cforall Version 1.0.0 Copyright (C) 2023 University of Waterloo
3//
4// The contents of this file are covered under the licence agreement in the
5// file "LICENCE" distributed with Cforall.
6//
7// raii.hfa -- PUBLIC
8// Utilities for advanced RAII (constructor/destructor) patterns
9//
10// Author : Mike Brooks
11// Created On : Fri Sep 22 15:00:00 2023
12// Last Modified By :
13// Last Modified On :
14// Update Count :
15//
16#pragma once
17
18// Provides access to unititialized storage.
19// Intended to make cheap delayed intialization possible.
20// Similar to uC++ uNoCtor.
21// Regardless of what constructors T offers, the declaration
22// uninit(T) x;
23// makes x:
24// - assignable to T,
25// - be, at first, uninitialized, and
26// - receive a T-destructor call when x goes out of scope.
27// This sitation means the user becomes responsible for making a placement constructor call
28// on x before its first use, even if this first use is the implicit destructor call.
29// This sitation contrasts with that of
30// T y @= {};
31// in that y does not receive an implied destructor call when it goes out of scope.
32// This sitation contrasts with that of
33// optional(T) z;
34// in that z receives a T-destructor call conditionally upon the runtime-tracked state,
35// and that z's assignability to T is guarded by the runtime-tracked state.
36//
37// Implementation note: the uninit RAII that follows is a parade of cfa-cpp quirk exploitations.
38//
39forall( T* )
40struct uninit {
41 inline T;
42};
43
44// Parameterless ctor: leaves bits within uninitialized.
45forall( T* )
46void ?{}( uninit(T) & this ) {
47
48 // Implementation takes advantage of CFA-available unsoundness.
49 // It could be called a bug; if it's fixed, then uninit needs an escape hatch,
50 // or to find a different loophole.
51
52 // Fundamental unsoundness: Here is a constructor for a T, whatever T is.
53 // Sound compiler reaction: We don't know what fields T has,
54 // so the programmer is surely failing to initialize all of T's fields,
55 // for some choice of T.
56 // Current compiler reaction: Ok, it initializes all the fields we know about.
57 void ?{}( T & ) {}
58
59 // Now for some ado about nothing.
60 // We need to call the above constructor on the inline T field.
61 // Becasue the compiler holds us accountable for intizliing every field of uninit(T).
62 // We are happy to do so and are not trying to get out of it.
63 // But the compiler doesn't recognize this form as a field initialization
64 // T & inner = this;
65 // ( inner ){};
66 // And the compiler doesn't offer this feature
67 // ( (return T &) this ){};
68 // It does recognize this form...
69
70 ( (T&) this ){};
71
72 // ...though it probably shouldn't.
73 // The problem with this form is that it doesn't actually mean the Plan-9 base field.
74 // It means to reinterpret `this` with type T.
75 // For a plan-9 use in which the base-type field is not first,
76 // this form would send the wrong address to the called ctor.
77 // Fortunately, uninit has the base-type field first.
78 // For an RAII use in which the constructor does something,
79 // getting the wrong address would matter.
80 // Fortunately, ?{}(T&) is a no-op.
81}
82
83// dtor: pass-through
84forall( T* | { void ^?{}( T& ); } )
85void ^?{}( uninit(T) & this) {
86 // an inner dtor call is implied
87
88 // In fact, an autogen'd dtor would have sufficed.
89 // But there is no autogen'd dtor because no T-dtor is asserted on the struct declaration.
90 // Adding assertions to the struct decl would make the intended ctor (implemented above)
91 // a less preferred candidate than the declared, but undefined, (ugh!) autogen ctor.
92}
93
94// Optional explicit inner-ctor invoation helper.
95// Generally optional, because 1 and 2 below are equivalent:
96// struct Foo;
97// void ?{}( Foo &, X, Y, Z );
98// uninit(Foo) uf;
99// ?( uf ){ x, y, z }; // 1
100// emplace( uf, x, y, z ); // 2
101// Is necessary for reaching a parameterless constructor
102// void ?{}( Foo & );
103// ?( uf ){}; // calls ?{}( uninit(Foo) & ), which does nothing
104// emplace( uf ); // calls ?{}( Foo & ), probably what you want
105forall( T*, Args... | { void ?{}( T&, Args ); } )
106void emplace( uninit(T) & this, Args a ) {
107 T & inner = this;
108 ( inner ){ a };
109}
Note: See TracBrowser for help on using the repository browser.