I actually think it depends and maybe I'll compete for the weirdest answer here among my C++ peers. That said, if you are already using C++ and C++'s rich type system, I think it'd be crazy not to use RAII for the most part even without exceptions.
The Nuisance of Lacking RAII When Designing and Using Interfaces
You generally don't wanna have to write icky functions that return resources you allocate inside the function that clients have to free/close/destroy manually and externally. At the same time it can be time-consuming to invert the design and have the client allocate the resources and pass them by parameter for a function to use (fill out a buffer allocated by the client, e..g), and still with the client's responsibility to free/close/destroy (just arguably a bit cleaner since the client at least created/opened/allocated the resource himself). Just designing a function that returns a variable-length string whose contents are determined inside the function when it is called is time-consuming and always a little icky either to use or to implement or both lacking RAII.
These are daily nuisances designing and using interfaces in C that you don't have to deal with in C++ if you have resources that clean up after themselves when they go out of scope. The time it takes to just give something a few constructors and a destructors often far outweighs the extra nuisance of having to deal with the above.
Dangers Lacking RAII
And similar thing for just basic things like a scoped mutex. It definitely safeguards you against some potential future bugs to make a mutex explicitly unlock itself when it goes out of scope. It might be easy enough to personally avoid bugs in such cases without exceptions involved when writing the code the first time around and testing it, but some colleague might hastily introduce some early return
statement in the future during crunch time into that function and forget to unlock
the mutex if that's explicitly required. However probable or improbable that is, it's nice to have that scoped mutex around.
The Benefit of a Dumb Type System
But I'm a weird type that loves both C and C++ and bounces back and forth between both, and there's one thing I find a lot easier to do in C and that's implementing low-level data structures that can work with just about any data type. It's because in C you don't have a rich type system with objects that can have vtables and dtors and ctors and you don't have exceptions. You can generally treat data types as just bits and bytes to memcpy
here and memmove
there, malloc
memory for anything here and realloc
there. And the reason we can do that confidently in C is because the type system is so dumb and lacks all these features.
Of course the data structures I design in C are not convenient to use. They lack type safety and often deal with void*
pointers, they have to be manually destroyed, etc. They're just really convenient to implement and make cache-friendly while minimizing heap allocations since they let me really focus hardcore on memory layouts and representations when I can just look at data types as bits and bytes to be shuffled around. Meanwhile back in C++, it's not easy even to implement a growable array container like std::vector
properly while providing exception-safety and using placement new
and manual invocations of destructors and so forth while allocating and freeing through std::allocator
. However, std::vector
is way, way more convenient and safe to use than any data structure I've designed in C.
So there's a particular kind of convenience to having a type system which lacks destructors and constructors and so forth, and C wouldn't necessarily be made better for what it excels at if it gained destructors and constructors to allow RAII-conforming resources, since suddenly all sorts of existing daily C functions like memcpy
would no longer be sane to use anymore. They'd suddenly become the most deadly, error-prone functions under such a type system, as they are in C++.
Therefore I actually think there's an argument to be made that in some low-level domains, RAII could actually be a hindrance, though only for these very low-level domains. Meanwhile it's extremely beneficial for everything else, with or without exceptions. Exceptions turn RAII almost into a requirement, but excluding these very low-level domains, they are still extremely useful regardless.
Trivially Constructible/Destructible UDTs
Sometimes I wish the struct
keyword in C++ reduced a structure to a plain old data type that is trivially-constructible and destructible (the type of thing we can safely memcpy
around, e.g.), with compiler safeguards that prevent it from storing any data members which aren't. I would find less reasons to use C if so, since at that point we might write generic containers which can only work with such structs
, and not classes
, without relying on type-traits to determine whether a generic type is trivially constructible/destructible. Because the appeal to C was never so much about the lack of RAII so much as being able to easily assume that many data types we store in our data structures and work with daily don't need it. It's nice when implementing data structures to know you can, say, free
a contiguous block of memory for N
elements without having to loop through them and invoke destructors. In C++ you often have to err on the safer side and assume that just about everything does need it (or will need it in the future) to the point where, say, memcpy
is strongly, strongly discouraged from being used anywhere by anybody.