> The standard states you cannot make [two] unique_ptr<> with the same pointer value within the same compilation unit.
> 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.
Yeah this just comes across as not understanding pointer ownership, or memory allocation in general. It's not a trivial issue, especially if you're dealing with weird C libraries. Sometimes you don't own a pointer you allocated. Sometimes you need a custom deleter.
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.
Yeah, with the QML example, if the programmer instead had used a raw pointer, he would still have a problem. Since if he chose to call free, he would have the same crash that the example had. And if he chose to just never call free, he would have a memory leak.
> Someone thinks it is a good idea to use Smart Pointers then passes the value to library functions written in C and other languages
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?
Exactly. In the QML example, it sounds like some function is creating a QML form and passing in pointers to objects which are not copied but stored by QML (or Javascript). The form template is persistent and is reused when new windows are created, perhaps.
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.
Agreed. This is a very odd rant against smart pointers that is essentially a rant against the strawman.
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.
Well, yeah, a language feature to improve program correctness won't work if you use it wrong. But in both examples, just from the interfaces, this should be caught in code review without even having to read the libraries being called. Anytime you take a raw pointer to a smart pointer owned object, you should question yourself, what happens to the lifetime of my object. Because you just took the safety off C++s classic footgun of memory management, and if you are not extremely careful, you are going to lose a foot.
Gee, another rant which can be summed up by "all abstractions are leaky". Congratulations for the insight, unfortunately it's not new, so not too interesting, but let's still dive into it.
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.
The point he was trying to make is the books for decade are mantra it as safe even though it is not. The new thing in this article rant is set a unique value before nulling and freeing allowing easier tracking of mysterious crash later. When I look thru hundreds of juniors code reviews with C++ coding, I have never seen one doing it (this is with Ivy Leagues or just Udemy/Coursera self-taught background). Even senior C++ devs dont do it. They either depend heavily on profile debugging tools or printf out at certain lines. It is a good article just maybe not for decades experience C++ coders or academically inclined one that dont do large production coding low level algorithmic much (their response is like yours not-perfect use-others-it-is-your-brain-problem)...in C++ (doing many API calls programming don't count...they are merely API programmers). Anyway, companies I worked with already migrated to rust and haskell/erlang to avoid this kind of C/C++ issues - same or even faster speed (some parts code in asm) and 100x times safer and more productive than just C++. C++ in 2023 is just badly hotch-patch up language - ugly, inconsistent, lack advanced features, even the so called biggest reason using: speed totally not match against zig/asm/even Java in some code implementation. At this point using C++ is mostly a laziness of programmers to record the existing "proven" C++ libraries a bit like Cobol - seen these conclusions over 20+ companies I audited across the world. A lot of time, when analysing using C++, it is fast if you measure specific execution timing but then fall apart and burn down the company with lawsuit-litigations when factor in bugs and debugging. Rust and Haskell and especially Erlang (Scala too) just steam-rolled C++ when you do total time (coding-execute-maintain-debugging). Whatsapp is good example of my case, you will never be able to create Watsapp with C++ in those short time and very limited resources - rather than just a couple of programmers for programming, you need a committee and specialized team internal and sometime external to do debugging in C++. C++ was designed for the 486-pentium era where bugs is not important but speed is. Today, just go buy a better hardware and charge end willing paying customers more (military/telecom/medical). C++ is just not worth it unless maybe you need to translate it into another programming language.
Well for me it looks less like another rant and more like some practical advice preluded with basic introduction:
‘ 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 …
Making them raw pointers is an enormous amount of work, and "delete them when you know it is safe" very likely is harder than finding the misuse of the smart pointers.
> I have to do mumbo-jambo dance to make it shift gears as I wanted.
I get the idea the author has never worked with a c++ codebase of anywhere close to 1000 lines. I can manage raw new and delete on that scale. As a senor developer with a couple decades of experience I can probably pull it off for 10000 lines of code. But I work with 20 million lines of c++ and there is no way for a human to get manual memory management right at that scale without help from smart pointers. Sometimes I need an intrusive smart pointer which I write manually, but I still confine it to a few thousand lines at most that I need to audit to ensure it is right.
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.
There's also the old story about how memory leaks on missiles don't matter as long as you're leaking slow enough to not run out of memory before they explode.
I worked with a guy — he's long deceased — whose doctoral dissertation was for missile guidance systems back in the 60s. The title was something like "supersonic ballistic landing trajectories for light civilian aircraft"; since, obviously, he couldn't publish it as "... for ICBMs".
He was probably right. When I write small, short-lived programs I mind my memory because it's a muscle I need to retain and I don't want to pick up bad habits.
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.
My economics-studying roommate had an optimization problem that required a lot of RAM. He had 32 GB (that was >10 years ago). His program crashed with an out of memory exception. He was rich enough to simply upgrade his system to 64 GB.
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.
It's a good example and I think it generalizes. For instance, I'm capable of some minor repairs for appliances, but if some problem seems to be contained in the PCB and I can just replace it for $50 (or whatever) I'll just do that. I don't own an oscilloscope and only barely know how to use it--so yes, I'm sure I've spent $50 when I could have just desoldered some 25 cent part, but it's far more costly for me to learn how to diagnose and fix these sorts of problems than to just buy my way out.
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.
That is true, but then if you need to debug some memory usage or a memory leak, you have to wade through all the valgrind false positives due to people not bothering to clean up at the end of the program.
About 24 years ago I built a webmail service based on C++ cgi's. FastCGI existed, but that required perfect memory management. CGI let us do what your instructor did, and on one hand it saved us from dealing with leaks at all, and on the other it reduced the performance overhead of going for CGIs - much faster to deallocate the whole heap in one go.
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.
It got surprisingly fast actually. We also did things like statically link it which drove the overhead of restarting down further (when the dynamic linker is a measurable proportion of your total execution time....), and I really liked knowing we had a fully clean slate on every request.
I mostly do Ruby these days, and the one thing I strongly dislike is the horribly slow startup...
And even the 1000 lines only work if there never is another developer touching the code. I can easily hold my own invariants in my head at that size, but if anyone else also contributes, there will be unknown invariants that I will miss.
Smart pointers are, besides other advantages, also a great communication tool.
That said, I prefer Haskell or Rust in that regard :)
Am I misunderstanding something or does the author seem to be conflating garbage collection with the automatic memory management C++ enables via smart pointers? Those are two entirely different things.
I usually phrase this, when hearing people talk about safety in C++, as the mold coming through the wallpaper. The need to take raw pointers from "smart pointers" keeps coming up, because there are too many C APIs.
It's the wrong abstraction for the use case. In the case of QML, Qt has its own memory management paradigm and you can play along or fight it. If you fight it, you need to know what your code and its code is doing.
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.
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.
You can't copy a unique_ptr. You're making two distinct unique_ptrs that point to the same thing. You can avoid that by... Well, by not doing that. Thankfully, the obvious way to do that, by copying the unique_ptr, is not allowed, thus making it harder for it to happen on accident.
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.)
33 comments
[ 1.5 ms ] story [ 82.1 ms ] thread> 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.
[0]: https://eel.is/c++draft/unique.ptr
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.
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.
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.
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?
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.
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.
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.)