The Myth of Smart Pointers
logikalsolutions.com
logikalsolutions.com
So, smart pointers are leaky, so use normal pointers instead. If we ignored all the problems that normal pointers have this may even look reasonable. After all, if we have a leaky abstraction, maybe we could just use the non-leaky foundation? But pointers have problems. Use-after-free, double-free, memory leaks. All of this happen with pointers far more often. Which is a bit of a problem if you want to write reliable software.
Are smart pointers a perfect solution? No. Are they better than "use raw pointers everywhere, cause in some cases the abstraction provided by smart pointers will fail?". Well .. yeah.
‘ When trying to track down a ghost like crash, nuke all of the Smart Pointers in the code. Make them raw pointers and delete them when you know it is safe. Prior to the manual delete, put some unique values out there prior to the deletion and be certain to null out the pointer you just deleted. The unique values make it a lot easier to track down which pointer was actually the culprit. “
With modern attitude of putting ‘Smart’ in front of every primitive automat I usually expect dealing with two dumbs - automat itself and another dumb who is going to relay on it.
The worse part about “Smart” solutions is that now I have to dedicate part of my life for exploring the realm of idiocy that was driving someone else who had made this particular “Smart” device/technology.
Instead of learning how to shift gears in the car and shift them when I need it I need to learn how some builder of automatic gear was thinking “when it is better to shift gears when driver is dumb” . Then (since automatic usually never work as I wish) I have to do mumbo-jambo dance to make it shift gears as I wanted. Something that was done easily with manual stick already.
Automatic works only when it is really perfectly done which is a rare case . So as a builder of automatic/smart solutions - be modest ! Leave “Manual’ option available. Just in case.
The only thing I usually wish from “Smart” solutions is the option to switch this stupid thing off unless it really works perfectly but even then …
> I have to do mumbo-jambo dance to make it shift gears as I wanted.
You do not have to do that.
std::vector<float*> myVec;
myVec.resize(1024);
for (int i = 0; i < 1024; ++i) myVec[i] = new float(0);
There were no `delete`s or `free`s anywhere in the codebase. I asked him what he was thinking and his response was: (1) it's not a server, and I know there's enough room on the computer; and, (2) the OS will free all of the memory for me, anyways.For an academic writing a one-off program just to get something done, all manner of bad practice is if not desirable, at least acceptable.
To my CS eyes, this sounded weird. Turns out he was initializing arrays manually instead of in a loop, so the compiler didn't recognize possible optimizations in the program. I changed his program and it ran twice as fast in half the memory. But he had already fixed his problem without 2 years of studying CS.
Software is about solving problems. Professors in particular have exams, staff, their own work, student requests, external mail and international collaboration to worry about. I bet that once the program stops working, he'll make a student fix the problem within 3 weeks. That's a fine approach and way better than learning C++ yourself.
I learned how to do one profession to a high standard and maybe aspects of some others to a lesser standard, but it's just smart to be mindful of how we want to spend our limited skill-development budget.
We'd manually deallocate particularly big allocations, but otherwise we knew the process would die within fractions of a second anyway, so the increased memory use was fairly limited.
We served a couple of million users with that using servers with a combined capacity lower than my current desktop.
I mostly do Ruby these days, and the one thing I strongly dislike is the horribly slow startup...
Smart pointers are, besides other advantages, also a great communication tool.
That said, I prefer Haskell or Rust in that regard :)
Yeah okay, sure.
Those can't be misused ... right?
The difference between code with unique_ptr and code without is that you can statically analyze the unsafe usages of unique_ptr, whereas it's basically not possible to do so for raw memory allocations. In fact, it solves most of the problem to just use make_unique wherever possible; you might be able to blanket-ban the normal constructor in your codebase and only use it with an exception.
The unique_ptr itself upholds all of the invariants that it promises to, which makes it a useful safety primitive. It, however, exists in a very, very memory-unsafe language, so of course, it's completely possible to blow past that and do anything else around it. This is unfortunate, but literally unavoidable if you want to stick to C++. In the meantime, if you're going to use C++, consider trying to limit yourself to the safe and correct subset of unique_ptr usage. You won't need as many exceptions as you think. (A neat thing I learned somewhat recently is that you can upgrade a unique_ptr directly into a shared_ptr. This is helpful because while shared_ptr is ideally very seldom used, it does mean that at least you don't have to eat the cost of shared_ptr until the exact point that you know you need it.)
If you got a low-level pointer out of the smart pointer container in order to pass to a foreign library, then you're not strictly using smart pointers. You violated the encapsulation in order to get a "dumb" pointer out, and the issue is related to that.
This is a complete strawman.
"Don't use a garbage collected, managed languages, they are a myth. Why? Because a pointer to some garbage-collected object ends up in foreign libraries written in C, which can hang on to it past reclamation ..."
Well, gee whiz, you will just have to learn about the exact sharing situation with the foreign code (how long does it retain the object) and do what is necessary.
> Make them raw pointers and delete them when you know it is safe.
That's completely stupid. If you know when it's safe, it means you must know when the third party library is no longer using them. If you know that, you work that knowledge into the smart pointer solution.
Smart pointers, by themselves, have their own idea of when it is safe to delete something. That idea is very clear and obvious. If it doesn't match the reality of what is safe when, then you have to augment the logic. That's it.
If a foreign library directly accesses an object that you track with a smart pointer, you can create a proxy object representing that library, and that proxy object can hold on to a smart pointer on behalf of the foreign library.
The complexity of dealing with the foreign library could be entirely minor (and self-contained) compared to the overall resource management problem of that smart pointer class; you could be making a lot of unnecessary work for yourself (and future bugs) by uprooting the smart pointers in favor of dumb pointers.
What are you going to do if part of the reason the smart pointers are there is that they are providing needed exception safety?
Another article on the author's site references a different complaint about QML, that it sometimes does assume ownership of objects, if they are normal QObjects without parents. This led to them getting reclaimed by the window being closed. There is, of course, documentation for when this might happen.
One thing that's generally true in C++ is that the user of a library has to play along. If the library wants to allocate your objects, let it. If it wants to take ownership of your objects, expect it. If it doesn't take ownership, you need to understand their lifetimes. It's definitely one of the frustrating things about C++, but I wouldn't expect a lot of complaints about one library or another unless they were being surprisingly capricious, taking ownership on Wednesday, not using your allocated objects on Friday, copying on Monday.
The most relevant line is this: "to most other languages you are simply passing the address of a buffer". That's true! And so for the love of all that's holy, don't transfer ownership. Simply pass a (non-owning, defined-lifetime) pointer to your data to the relevant dependency.
Have asynchronous issues? Need to extend lifetime sometimes? Well, yeah, unique_ptr is not going to help you there. It's not magic.
But if you think about ownership you can spot the bug in a proper code-review. Smart pointers are about ownership, where a unique_ptr has unique (one owner) ownership. If you create a second owner, which happens in fun1, then that second owner is cleaning up before (at the end of scope -> RAII), it is cleaned up in by its first owner (resulting in a double free)
A guideline which is often used, is:
pass by reference -> not transferring ownership
pass by raw pointer -> transferring ownership
So having a function with a raw pointer argument flags transfer of ownership, but isn't necessarily so. The transfer really happens on line 44.
A unique_ptr actually guards you from that, because it is move only (cannot be copied). Using a std::unique_ptr on the interface of fun1 would have caught the problem at compile time, because an explicit move would have been needed to tranfer ownership, which would have put the original object in an valid but unspecified state. This would result in the original owner not cleaning up the resource, and therefore not resulting in a double free.
By using the get function on the unique pointer you actually remove the guards of having one owner.
There are a lot of things wrong with this code, which after being solved I don't see how you would need a pointer at all. Otherwise, you need to know about the guidelines of using smart pointers.
So my conclusion after reading this code is: This looks like C code with some C++ features sprinkled in. If you want to write C++ you need to know a lot about the language and its guidelines. Thinking about ownership is hard but necessary in any language. Having the ownership correct would have made it easy to add smart pointers. If the ownership is not clear, using raw pointers will not save you.
> We got this error because the “compiler will only allow one unique_ptr per address” mantra is mostly kaka.
I'm curious where the "enforced one unique_ptr per address" idea comes from. I don't think I've ever heard of it before, and I don't see any language implying that in the current (?) draft standard [0]. If anything, I think I'd be a bit surprised if such language existed - after all, creating multiple unique_ptrs to the same place is technically harmless as long as you call release() on enough of them so you don't end up multi-deleting the common object.
This also seems like one of those things where "cannot" is more "doing this is a bad idea and you may run into UB" than "the program will not compile if you do this" due to guaranteed enforcement being statically infeasible.
The other issues including the QML stuff sounds more like unclear ownership semantics across libraries. I feel the advice here is slightly off-target; if you don't know the ownership semantics of the libraries you're using neither using smart pointers nor avoiding smart pointers are going to be a magic bullet to solve your issues maybe unless you take things to the extreme and allocate everything on startup.
> Someone thinks it is a good idea to use Smart Pointers then passes the value to library functions written in C and other languages. Usually these things are in their own threads.
Like this. The issue isn't the smart pointers, it's that the ownership semantics of the library function(s) weren't properly accounted for. unique_ptr::release() exists for a reason, after all!
> When trying to track down a ghost like crash, nuke all of the Smart Pointers in the code. Make them raw pointers and delete them when you know it is safe.
I'm somewhat surprised this is the advice given rather than something like sanitizers and/or valgrind, since those could be significantly less invasive than ripping every smart pointer in a program. Then again, the author states they work on medical devices, and I'm not sure whether sanitizers or valgrind are available for those.
In addition, the second sentence is pretty much "don't write bugs", which is technically correct but not exactly helpful; why not "look at your smart pointers again and ensure they aren't violating their ownership guarantees"? You would basically be doing the same thing - lifetime analysis - either way. Heck, you can even re-scope your smart pointers so they'll only go out of scope "when you know it is safe".
I don't think I've heard "near heap" and "far heap" used in such a way before, either. Not that it's bad; just different.
Within your own code you can just use std::make_unique and rely on the compiler to catch errors, but when passing raw pointers to libraries you really gotta read the docs and work carefully.
I mean, why do people consider C++ to be an advanced-level language? Because it's full of these foot guns. Yet the author is a consultant who seems to work mostly with C++, so presumably they're familiar with the cargo-cult programming style of many entry-level C++ devs and the impedance mismatch between various frameworks.
It's just weird to single out smart pointers as the cause of the problem. In this case, someone made a use-after-free. Maybe it would be a memory leak done another way.
I think the Boehm GC would actually be handling this case pretty well, but C++ people are often allergic to GC, even when it would serve well.