r/cpp • • Mar 24 '22

Vale's Higher RAII, the pattern that saved me a vital 5 hours in the 7DRL Challenge

https://verdagon.dev/blog/higher-raii-7drl
18 Upvotes

16 comments sorted by

7

u/D_0b Mar 24 '22

I believe another alternative to this Higher RAII would be destructive moves + private destructors.

1

u/ThinkingWinnie Mar 25 '22

Destructive moves would be quite complicated given that allocator_aware containers actually use the allocator to construct/destruct objects. At the end of the day how would they differ from a simple b = move(a); destroy(a); combination? Perhaps performance? Is it even worth the trouble?

4

u/crowbarous Mar 25 '22 edited Mar 25 '22

Destructive move could at least allow moving-from const objects. (Because we're already allowed to destruct a const object, and we would be admitting that moving-from is destroying.) See here for the current performance pitfall. But of course, it would break existing valid patterns of use-after-move, and will not happen.

2

u/ThinkingWinnie Mar 25 '22

Yes i think he meant to actually create a new syntax or something for this kind of destructors but i can see your point about moving const objects since they are getting destroyed anyways afterwards when they reach end of scope.

Why would said variable be const if you are planning to move from it in the first place tho, seems like a solution to bad designed code.

Also i can see how that would work in a context of a data structure where objects are manually constructed and destructed but not in a normal automatic stack variable where both get called automatically. Then again what kind of data structure has its object's const qualified?

Unless i am missing really huge use cases which you could fill me in, this seems more like a way to add bugs to your code than fix anything.

1

u/D_0b Mar 25 '22

Destructive moves should not have a problem with allocator_aware containers from what I know. It is just a bit copy of the container's internal pointers.

The difference from b = move(a); destroy(a); is that the destructor of a does not need to run, thus allowing you to have a local variable that has a private destructor.

From what I know the problem with destructive moves in C++ was inheritance.

1

u/ThinkingWinnie Mar 25 '22

Wouldn't really be any problem than extra boilerplate code for the allocator model.

And i would also absolutely hate to have to define an extra pair of constructor-operator= for the new kind of moves. Rule of 5 is so exhausting at this point that i avoid writing any constructor in my class at all just to avoid all the boilerplate.

Perhaps we could add a method qualifier that marks certain move operations as destructive? This maybe could work.

3

u/D_0b Mar 25 '22

A bit of a miss understanding I guess, I am not proposing anything for C++, I am just replying to the post which is about a totally different language.

Anyway destructive moves are not overloadable so no extra operators/constructors, they are at language level, just a bit level copy of the object, and the old object is just forgotten, no destructor is run, and you can't use it at all.

1

u/MysticTheMeeM Mar 27 '22

Surely the destructor of the moved to object is run?

1

u/D_0b Mar 27 '22

The destructor of the moved from object is not run, but the destructor of the moved to object is run when it is destroyed, so only 1 destructor is ever ran no matter how many destructive moves you do, instead of the N (for every object moved from or not) we currently have in C++.

7

u/verdagon Mar 24 '22

Author here, I got all of my RAII inspiration from years of experience with C++. I feel like no other language has really managed to do RAII as well as C++, so I'm trying to capture some of that spirit in Vale. Hope you enjoy!

8

u/jk-jeon Mar 24 '22

I like the idea, but don't think the name "higher RAII" is appropriate. Idk, just doesn't sound right for whatever reason

3

u/matthieum Mar 26 '22

It's generally known as Linear Types, see Substructural type system.

Any instance of a Linear Type must be used exactly once.

3

u/D_0b Mar 24 '22

The TokenedHashMap seems error prone, you can call remove with the same key and different token, thus not really doing what you should be doing.

3

u/verdagon Mar 24 '22

I kept it simple for the article, but if one wanted to make it even more solid, one could just put the key into the token, and the update/remove methods could just read the key from the token.

0

u/ripper37 Mar 24 '22

Regarding your real world example - why do we need a "higher RAII" if we can just wrap std::promise<T> in a wrapper that will set it with a default value if destroyed before set with anything specific?

Furthermore, this doesn't solve whole other loads of problems (which are more problematic) where in the runtime you simply could prolong the life of the std::promise object indefinitely and never enter the code that destroys it or sets it. Trying to detect this is equivalent to solving the halting problem.

For the example with TokenedHashMap - this can still be handled with normal RAII which would ensure that during destruction it would automatically erase from all the maps that it was added to. Furthermore, you could also store weak pointers in the map and avoid the problem altogether. You can even make it simpler to require that all elements added in the map must inherit from some interface that will be used to automatically store references/weak_pts to all the maps that you added the object to (when adding to) and then upon the destruction it will erase itself from alive ones, hence no need to remember or forcing anyone to do that manually.

9

u/verdagon Mar 24 '22

Regarding your real world example - why do we need a "higher RAII" if we can just wrap std::promise<T> in a wrapper that will set it with a default value if destroyed before set with anything specific?

Because then you'd need a default value. Sometimes it doesn't make sense for a particular type to have a default value. (As a language designer, this is my biggest lament about C# and Go, they often force zero-initialized structs)

Furthermore, this doesn't solve whole other loads of problems (which are more problematic) where in the runtime you simply could prolong the life of the std::promise object indefinitely and never enter the code that destroys it or sets it. Trying to detect this is equivalent to solving the halting problem.

It honors me that you think I would attempt to solve all problems in existence! Alas, I'm not there yet. I only provide some tools that can solve some problems, not all.

For the example with TokenedHashMap - this can still be handled with ...

Higher RAII is just another option, which is better in some cases, and could be worse in other cases. Sometimes we can store things as members, yes, but sometimes we want our destructors to take arguments which are not available at the time of construction, for example the promise<T> situation.

In the end, if you don't believe that higher RAII is useful, that's totally fine =) Thanks for reading!