When I got introduced to C++ via Turbo C++ 1.0 for MS-DOS in 1993(!), I saw no longer a reason to do that other than being forced to deal with legacy C code.
Turbo C++ already came with collection classes, and RAII has been a thing since C++ no longer was named C with Classes.
Unfortunately re-education takes generations when a language happens to be copy paste compatible with C.
The problem I see personally with unique_ptr is that it's still doing small-scale memory management. It only removes some of the typing. (And for non memory management related tasks, it add significant amount of typing as well, compared to naked pointers).
As far as I'm concerned, it's automatic memory management (with lots of control). There's also the other end of the automatic memory management spectrum, garbage collection.
CVE database is updated almost every week on those "best" practices.
Why though?
I use C++ and I use a variation of malloc and free all the time. The arguments I hear against malloc is:
1. You'll leak memory.
This is super easy to solve in literally 300 lines of code or less. Just have a vector of your memory allocations in debug mode. Then call a custom allocator and free function that adds an allocation and removes an allocation from this list. At the end of the program, print out any allocations that are in the list that never got freed. You can use macros to print the exact file and line number that allocated the memory and then fix it very quickly.
2. It's not typesafe.
So just use new if you're really concerned with type safety and then overload the operator to get the benefits I described above.
3. It's unsafe.
How is this any more unsafe than RAII? Aren't there still the same risks involved?
4. Nullptr exceptions.
Once again, same problem with smart pointers. Technically if you're not transferring ownership you should be passing a const reference or a raw pointer rather than a smart pointer anyways. So the end result is the same.
Maybe I'm missing some huge thing, but I have not heard any convincing argument for using shared pointers over malloc. (And yes I know you should really use new if you're not using POD because of constructors and all that stuff, but I'm equating new and malloc for brevity).
Lastly, I've found that when I use shared pointers I get lulled into a false sense of security and get sloppy. It's a good thing to really think about why you're making a specific memory allocation. Doing it manually forces you to consider whether there's a better option like static local storage or something. In addition, as long as you have a memory tracker implemented like my answer to 1, you never have to spend time worrying about memory. Just today, I forgot to free some memory and I was immediately notified when I ran my program and it printed a warning after I closed it with the leak. I cleaned it up in 5 minutes, end of story.
Edit: I forgot to mention, what's the one huge pro to using new/delete or malloc/free over smart pointers? Explicit control over when memory gets allocated and deallocated. If you use smart pointers everywhere, won't you eventually have the same issue as GC? They go out of scope at random points in your programs lifetime, and you get "random" lag spikes from memory cleanup. The whole point of using C++ is to have control over when this stuff happens! If you don't care when it happens, why not use the GC?
>
> This is super easy to solve in literally 300 lines of code or less. Just have a vector of your memory allocations in debug mode. Then call a custom allocator and free function that adds an allocation and removes an allocation from this list. At the end of the program, print out any allocations that are in the list that never got freed. You can use macros to print the exact file and line number that allocated the memory and then fix it very quickly.
I've got similar macros for C, but it is still less work to simply use valgrind.
Valgrind is easier, but for real-time applications I've found that it takes a huge performance hit for some reason, at least the last time I used it.
It does, because it runs the app in an emulator.
What I did in the past (around 2005) was to write replacement malloc/calloc/realloc/free calls, put them into a libmymalloc.so, and then set LD_PRELOAD to point to libmymalloc.so.
This way I could switch between my malloc routines and the system malloc routines without recompiling the program.
std::vector<int> allocation_counts;
auto get_mem(int n) {
auto* memory = your_malloc(n);
allocation_counts.push_back(n);
return memory;
}
This function has a trivial leak of "memory" that an RAII version wouldn't have, can you see it?...assuming that there's a matching free_mem(ptr, n) which undoes the actions of get_mem() of course. Also I'm ignoring the cofusing use of auto vs auto*, I haven't caught up with modern C++ enough to know whether this even makes sense ;)
...also, what would the better RAII version of this function look like?
std::vector::push_back can throw an exception, if for instance you just allocated 2 gigabytes of memory before and it does not have space to reallocate its internal storage:
std::vector<int> allocation_counts;
auto get_mem(int n) {
auto* memory = your_malloc(n);
// if this throws: the function exits there and "memory" is lost forever
allocation_counts.push_back(n);
return memory;
}
A simple way to "RAII-fy" it would be, assuming a `your_free`: // Typedef this somewhere
using your_ptr = std::unique_ptr<void, decltype([] (auto p) { your_free(p); })>;
auto get_mem(int n) {
// encapsulate in your_ptr
auto memory = your_ptr{your_malloc(n)};
// if this throws: memory's destructor is called which frees the memory
allocation_counts.push_back(n);
return memory;
}
Or if you want your custom RAII type for some reason: // memory.h or something
struct mem {
mem() = default;
// copy is not meaningful here
mem(const mem&) = delete;
mem& operator=(const mem&) = delete;
// move
mem(mem&& other): ptr{std::exchange(other.ptr, nullptr)} { }
mem& operator=(mem&& other) { ptr = std::exchange(other.ptr, nullptr); return *this; }
// allocation & deallocation
explicit mem(int n): ptr{your_malloc(n)} { }
~mem() { your_free(ptr); }
void* ptr{};
};
// your code
std::vector<int> allocation_counts;
auto get_mem(int n) {
auto memory = mem{n};
// if this throws: mem's destructor is called which frees the memory
allocation_counts.push_back(n);
return memory;
}But this is far fetched because people doing manual memory management don't usually rely on exceptions, and just abort the program when this happens, so the leak is pointless.
I think you really, really underestimate the amount of codebases where a large part of the code is "normal" C++ with exceptions and someone does one specific module with manual malloc / free out of cargo-culting some RAII fears.
Hell, even OP above talks about using vector along with their custom memory management scheme (which throws no matter if you use -fno-exceptions or not, as the actual "throw" call is in the precompiled libc++.so ; the code in <vector> just calls std::__throw_bad_array_new_length() indiscriminately : https://gcc.godbolt.org/z/fno8fYP7E )
I think the actual problem is that std::vector is doing its own memory management under the hood, which makes it hard to control what's going on from the outside. E.g. a design flaw of the C++ stdlib, not related to RAII.
plenty of cases where this isn't the case.
-fno-exceptions just means that you want to not be able to throw for your own code.
-fno-unwind-tables / -fno-asynchronous-unwind-tables makes your own code un-unwindable which is only safe if you are in entire control of the whole codebase, which is very rare in anything that is not simple games - no plug-in system, or any dynamic code loading or JIT'ing is safe as you don't know which user-provided function you call is going to throw.
Likewise, you could be in the opposite case: imagine you're making a C++ plug-in for, say, 3DS Max, where error reporting is done through exceptions ; even if your own plug-in code is built with -fno-exceptions and does not itself throw at any point, you still want exceptions that may be coming from a 3DSMax function you called to propagate to your code back to the exception handler provided by 3DS Max - and for this RAII of your own types is needed if you don't want your own resources to be leaked.