Great presentation Andre! Awesome refresher on something we tend to pay less attention to over time and needs to be revisited. Gives you new ideas on new possible uses.🙌
Kind of a pet peeve....it seems RAII has come to be known as the practice of resources being automatically released by a destructor. But that's not what the name says. Resource Acquisition IS Initialization means (or originally meant) that when you acquire a resource it is already initialized. You can call its member functions. That is, an object is initialized in it's constructor and is ready for use. This is opposed to an object that that has a constructor that doesn't do anything and requires you to call a separate Initialize() function before you can actually use it and call functions on it. The reason for doing the latter is to avoid using exceptions because if you do anything in the constructor that may fail you need to throw an exception. So does this mean that something like std::unique_ptr---which can be created empty and not initialized---is actually an RAII object itself? It's a holder or owner of a resource but not the resource itself. So are we talking about the holder of the resource or the resource itself? Surprisingly @35:17 the speaker tells you "Always initialize an object. That's part of the Acquisition is Initialization part." I believe he's referring back to the slide @26:07 and he is talking about the unique_ptr, the holder of the resource. Well I've "acquired" the unique_ptr but it's not initialized yet so what are you talking about? I think this has become all confused. If you want to talk about releasing resources automatically in a destructor then give it a different name. Stop calling it RAII because RAII is actually a different thing.
You're trying to logically construct a history that doesn't exist for RAII as a C++ idiom by claiming it originally meant what the words actually mean. It was always just named in a very contentious way, and always semantically meant what this speaker is describing, and people concerned with the syntax of naming the idiom always complained about it. You'll have more luck getting people to stop saying pin number than you will getting RAII's name changed; the way words are used is what matters, not the logical meaning of the theoretical combination of the words, and it is just something that has to be accepted or you will have a bad time.
The only way fclose can fail is if the handle passed to it is invalid. This has meaning in the context of plain procedural C code, but has no meaning in the context of RAII where the fclose in the destructor cannot be called with an invalid handle (as the RAII object wouldn't exist and thus cannot be destroyed). The handle could be corrupted after the object is instantiated but that is UB no matter what.
Not true. The fclose() may still have to flush any unwritten buffers to file (see documentation), which means it can also return any error that fflush() can. Also, the underlying OS close-file function on many systems can return error for similar reasons, which will be translated to C error and returned by fclose(). (see documentation for Windows CloseHandle() and Linux close(3) for example). If you ignore return value from fclose(), it is possible your code will report successful write, but the data wasn't written for some reason (eg. network drive connection was lost, USB stick has been removed...). This is an issue that most RAII introductions that use file handling as an example seem to ignore.
The custom-deleter template of unique-pointer is one of the things i have struggled with before. With shared-pointer it is better as you can (most of the time) easily change the deleter later. And yeah, there are various reasons why you would want to change the deleter. One example i have come upon and tried implementing (and failed miserably) was a uniquely owning pointer but NOT a unique pointer. To get something similar as what you can do with weak_ptr and shared_ptr. For this to work you need to be able to store and replace a potentially given deleter - which with unique_ptr is not possible. Why would you want a uniquely owning pointer with other observer-pointers? Debugging, gathering of statistics, performance-tuning, live modification and such. Took me some time to get a working prototype that acts mostly like a unique_ptr and a weak_ptr. You get a resource and it is managed. The class goes out of scope and it is released. You can set/get the deleter, you can take ownership away, you can move the ownership, but it can not be copied - so far so unique_ptr. But it also allows you to create an observer that knows of the livetime of the underlying object as long as it is held by the owning pointer. It can only be created from the owning-pointer or as a copy of an existing observing pointer. You can check the state of the underlying object and access it if and only if it is still alive. The one thing i still need to decide: How to provide a way for the observer to look at the object and be sure it did not just die right after i checked it is still alive. One idea was to make the observers act more like weak_ptr as in if they are actively observing they take on the role of "lifesupport" - if the object is observed and the owning pointer signals the end of the lifetime then any non-active observers can no longer turn active, and any active-observers basically turn into shared_ptr. Like this the owning-pointer is not impacted by this and there is no blocking, but the resource could stay alive longer than the scope would indicate. Other ways would be the observing-pointer only getting a copy of the held object if it is alive - only seeing a snapshot of how the object was at the time the snapshot was requested (if the object was alive). Better performance for the owning-ptr, lower memory-footprint if nothing is observed, the object gets destroyed right when the owning-pointer gets destroyed, but the object must be copyable and you always create a copy for observation. Or lastly - reade/rwriter semaphore/mutex. No problems with livetime or livetime-extension, roles of owner and observers very clear, but higher overhead overall.
9:35, yoi've heard of goto right? it solved that problem yonks ago, as for the deadlock situation, create a wrapper mutex object that you debug the details of, I did that and also solved the problem of "what if some sub function decides it needs to ensure an object is locked? Even if a caller has locked it for multiple calls of said function?" Normally that leads to a deadlock because the thread is then waiting for itself to unlock the mutex, with a custom mutex you can move the locking of an actual mutex to when you're obtaining the custom mutex the 1st time, using a lock count thereafter to indicate how many times the same thread locked the mutex, that combined with the thread's call stack gives you a clue where to look for offending code. As for the goto: int foo() { int err = -1; ... if ( ... ) goto free_mutex; ... err = 0; free_mutex: ... return err; }
You've heard of "goto considered harmful", right? goto does not solve this problem if an exception is thrown. Even if it did, the point is that it represents doing more work, and creating more opportunities for errors, for something that RAII enables you to do robustly and automatically. That is the core of the issue.
@@CharlesHogg no, goto is NOT harmful, as for catching exceptions I've already admited that the try/ctach pair is easier than the setjmp etc, doesn't mean I'm willing to put up with the unnecessary extras that ruined c++ rather than make it better, you want auto RAII? go use an interpreted language like lua, don't corrupt c/c++ with pointless semantics, you've taken what was simple and made it pointlessly complex
are you really managing your own resources if your constructors/destructors aren't immediately forwarding to extern "C" functions implemented in assembly? I think not lol
What actually happened to std::unique_resource? There was a talk about it and it's importance by Peter Sommerlad in 2018. Back then it was already over 5 years 'in the making'. Even now in C++23 it's nowhere to be found.
`template using my_resource = std::unique_ptr;` The `FILE*` example covered an example case using unique pointer; I generalized it with an alias template in the above snippet. Now the example in the talk can be simplified further: `my_resource test_file=fopen("test.file");`
@@faridmehrabi2761 This does not answer the question. std::unique_resource is designed to also cover non-pointer-type resources. Apart from that, std::unique_ptr 's deleter is just a function pointer. It cannot be used if the release-mechanism is not a plain function (like fclose), but, for instance, a member function of a different class. Say, for example, some graphics API where releasing a GPU buffer is done by calling a releaseBuffer member function on some device context.
@@AxWarhawk It should be possible if you manage to pass a lambda with captures as the deleter. After some experiments, I got this: auto deleter=[this](const char *obj) {printf("%p %d", obj, somefield);}; std::unique_ptr resource("abc", deleter); It's somewhat convoluted, but at least sizeof(resource) suggests that it should work. Personally, I'd write my own manager though.
Great presentation Andre! Awesome refresher on something we tend to pay less attention to over time and needs to be revisited. Gives you new ideas on new possible uses.🙌
Brilliant. Succinct and valueable. Examples were not hopelessly contrived, but useful, applicable and extensible. Thank you.
Very informative talk, thank you for sharing your knowledge and wisdom. I found your approach to talking about this easy to understand and follow.
Kind of a pet peeve....it seems RAII has come to be known as the practice of resources being automatically released by a destructor. But that's not what the name says. Resource Acquisition IS Initialization means (or originally meant) that when you acquire a resource it is already initialized. You can call its member functions. That is, an object is initialized in it's constructor and is ready for use. This is opposed to an object that that has a constructor that doesn't do anything and requires you to call a separate Initialize() function before you can actually use it and call functions on it. The reason for doing the latter is to avoid using exceptions because if you do anything in the constructor that may fail you need to throw an exception.
So does this mean that something like std::unique_ptr---which can be created empty and not initialized---is actually an RAII object itself? It's a holder or owner of a resource but not the resource itself. So are we talking about the holder of the resource or the resource itself? Surprisingly @35:17 the speaker tells you "Always initialize an object. That's part of the Acquisition is Initialization part." I believe he's referring back to the slide @26:07 and he is talking about the unique_ptr, the holder of the resource. Well I've "acquired" the unique_ptr but it's not initialized yet so what are you talking about?
I think this has become all confused. If you want to talk about releasing resources automatically in a destructor then give it a different name. Stop calling it RAII because RAII is actually a different thing.
It has a pair CAD-smthx talking about destruction but is rarely called. RAII is easier to say ig
You're trying to logically construct a history that doesn't exist for RAII as a C++ idiom by claiming it originally meant what the words actually mean. It was always just named in a very contentious way, and always semantically meant what this speaker is describing, and people concerned with the syntax of naming the idiom always complained about it. You'll have more luck getting people to stop saying pin number than you will getting RAII's name changed; the way words are used is what matters, not the logical meaning of the theoretical combination of the words, and it is just something that has to be accepted or you will have a bad time.
The only way fclose can fail is if the handle passed to it is invalid. This has meaning in the context of plain procedural C code, but has no meaning in the context of RAII where the fclose in the destructor cannot be called with an invalid handle (as the RAII object wouldn't exist and thus cannot be destroyed). The handle could be corrupted after the object is instantiated but that is UB no matter what.
Not true. The fclose() may still have to flush any unwritten buffers to file (see documentation), which means it can also return any error that fflush() can. Also, the underlying OS close-file function on many systems can return error for similar reasons, which will be translated to C error and returned by fclose(). (see documentation for Windows CloseHandle() and Linux close(3) for example). If you ignore return value from fclose(), it is possible your code will report successful write, but the data wasn't written for some reason (eg. network drive connection was lost, USB stick has been removed...). This is an issue that most RAII introductions that use file handling as an example seem to ignore.
The custom-deleter template of unique-pointer is one of the things i have struggled with before. With shared-pointer it is better as you can (most of the time) easily change the deleter later.
And yeah, there are various reasons why you would want to change the deleter. One example i have come upon and tried implementing (and failed miserably) was a uniquely owning pointer but NOT a unique pointer. To get something similar as what you can do with weak_ptr and shared_ptr.
For this to work you need to be able to store and replace a potentially given deleter - which with unique_ptr is not possible.
Why would you want a uniquely owning pointer with other observer-pointers? Debugging, gathering of statistics, performance-tuning, live modification and such.
Took me some time to get a working prototype that acts mostly like a unique_ptr and a weak_ptr. You get a resource and it is managed. The class goes out of scope and it is released. You can set/get the deleter, you can take ownership away, you can move the ownership, but it can not be copied - so far so unique_ptr.
But it also allows you to create an observer that knows of the livetime of the underlying object as long as it is held by the owning pointer. It can only be created from the owning-pointer or as a copy of an existing observing pointer. You can check the state of the underlying object and access it if and only if it is still alive. The one thing i still need to decide: How to provide a way for the observer to look at the object and be sure it did not just die right after i checked it is still alive.
One idea was to make the observers act more like weak_ptr as in if they are actively observing they take on the role of "lifesupport" - if the object is observed and the owning pointer signals the end of the lifetime then any non-active observers can no longer turn active, and any active-observers basically turn into shared_ptr. Like this the owning-pointer is not impacted by this and there is no blocking, but the resource could stay alive longer than the scope would indicate.
Other ways would be the observing-pointer only getting a copy of the held object if it is alive - only seeing a snapshot of how the object was at the time the snapshot was requested (if the object was alive). Better performance for the owning-ptr, lower memory-footprint if nothing is observed, the object gets destroyed right when the owning-pointer gets destroyed, but the object must be copyable and you always create a copy for observation.
Or lastly - reade/rwriter semaphore/mutex. No problems with livetime or livetime-extension, roles of owner and observers very clear, but higher overhead overall.
18:10, again simple to fix resource loops, just check the pointer/index is not the same as the one you started with
9:35, yoi've heard of goto right? it solved that problem yonks ago, as for the deadlock situation, create a wrapper mutex object that you debug the details of, I did that and also solved the problem of "what if some sub function decides it needs to ensure an object is locked? Even if a caller has locked it for multiple calls of said function?" Normally that leads to a deadlock because the thread is then waiting for itself to unlock the mutex, with a custom mutex you can move the locking of an actual mutex to when you're obtaining the custom mutex the 1st time, using a lock count thereafter to indicate how many times the same thread locked the mutex, that combined with the thread's call stack gives you a clue where to look for offending code. As for the goto:
int foo()
{
int err = -1;
...
if ( ... )
goto free_mutex;
...
err = 0;
free_mutex:
...
return err;
}
You've heard of "goto considered harmful", right?
goto does not solve this problem if an exception is thrown. Even if it did, the point is that it represents doing more work, and creating more opportunities for errors, for something that RAII enables you to do robustly and automatically. That is the core of the issue.
@@CharlesHogg no, goto is NOT harmful, as for catching exceptions I've already admited that the try/ctach pair is easier than the setjmp etc, doesn't mean I'm willing to put up with the unnecessary extras that ruined c++ rather than make it better, you want auto RAII? go use an interpreted language like lua, don't corrupt c/c++ with pointless semantics, you've taken what was simple and made it pointlessly complex
are you really managing your own resources if your constructors/destructors aren't immediately forwarding to extern "C" functions implemented in assembly? I think not lol
What actually happened to std::unique_resource? There was a talk about it and it's importance by Peter Sommerlad in 2018. Back then it was already over 5 years 'in the making'. Even now in C++23 it's nowhere to be found.
`template
using my_resource =
std::unique_ptr;`
The `FILE*` example covered an example case using unique pointer; I generalized it with an alias template in the above snippet. Now the example in the talk can be simplified further:
`my_resource test_file=fopen("test.file");`
RAII is IMHO one of the greatest features distinction of C++ from other languages. Prof. Stroustrup must be awarded for that one alone.
This why I like and still like C/C++ : Always remember me the symbolic calculus and abstract algebra.
@@faridmehrabi2761 This does not answer the question. std::unique_resource is designed to also cover non-pointer-type resources. Apart from that, std::unique_ptr 's deleter is just a function pointer. It cannot be used if the release-mechanism is not a plain function (like fclose), but, for instance, a member function of a different class. Say, for example, some graphics API where releasing a GPU buffer is done by calling a releaseBuffer member function on some device context.
@@AxWarhawk It should be possible if you manage to pass a lambda with captures as the deleter. After some experiments, I got this:
auto deleter=[this](const char *obj) {printf("%p %d", obj, somefield);};
std::unique_ptr resource("abc", deleter);
It's somewhat convoluted, but at least sizeof(resource) suggests that it should work.
Personally, I'd write my own manager though.