Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Modern C++ also discourages raw pointers and so you get references counters all over the place, essentially turning C++ into a garbage collected language.

This doesn't match my experience. It's true that modern C++ discourages owning raw pointers, but the solution is usually unique_ptr, not shared_ptr. Truly shared ownership is actually pretty uncommon IME, usually you can have one entity which obviously "owns" the object and then any other reference to it can be non-owning.

It's also worth noting that with std::move, actually changing the refcount of a share_ptr can be pretty rare even if you do have shared ownership.



This is not my experience. Most developers are just not very good at what they do, and the go-to smart pointers for not-very-good C++ developers is std:shared_ptr<T>.


This has been my experience as well - especially when C++11 came out. I have seen codebases where it has been "use std::shared_ptr for everything, becuase it is safer if/when we use threads". I know that doesn't make sense, but it just was the attitude back then.

Tbh, Back then, I didn't see a problem with it. Once i started chasing down weird bugs where objects aren't freed properly because no one knew which objects own what, I have been very cautious.


We had a few developers like that here when C++11 was introduced, but a few people gave them the smack down and now we rarely see shared pointers.


Hmm, that might be. Most of the C++ I've seen has been in LLVM, Google projects, projects where I'm the only developer or projects where I laid the groundwork which other people build upon, so I'm probably not mainly looking at the kind of code bases you're talking about.


OK but those people aren't going to magically write safe performant C either.


unique_ptr is pretty bad for performance as well. It is more complicated to use compared to raw pointers and encourages an OOP object-per-object piecemal code and data architecture. I've never seen a C++ program making use of unique_ptr that didn't give a strong smell of enterprise programming.


There's nothing more complicated about using unique_ptr than a raw pointer, it just expresses who's responsible for calling `delete` explicitly in code rather than implicitly through program flow.


There's nothing complicated? You have to 1) #include <memory> 2) Write "std::unique_ptr<My_Foo_Type> foo" instead of just "My_Foo_Type *foo" in every definition. 3) Are required to define My_Foo_Type as a class with a separate deleter, or provide a deleter template argument at each declaration. 4a) write "foo.get()" in various places instead of just "foo". or 4b) lend around the unique_ptr in various places, breaking modularization and increasing build times. 5) Be stuck with a non-POD type that you can't just memcpy() around. 6) enjoy the worse runtime because your program has just been artifically compartmentalized even more!

Sometimes you C++ guys are just blinded by the tale of "zero-cost abstractions".

unique_ptr, like the idea of RAII in general, binds together what should be separate. Data schemas and physical layout on the one hand, and memory and lifetime management on the other hand. What you get as a result is what you deserve: The idea of "more safe and maintainable" where the "more" isn't added to the equivalent non-RAII program. No, it is added to the more convoluted, less understandable, and thus inherently less safe and maintainable program. Who knows what the bottom line is (in my experience often safety is a bit better but I pray for you if you need to debug a problem, and maintainability is much worse), but out of interest in my own sanity I know my preference.


I really don’t see what the big deal is? Generally the only time you should be returning or passing around a unique_ptr is when you’re actually transferring ownership of the referenced object. Otherwise just dereference it and pass around a reference to the underlying object.


> Data schemas and physical layout on the one hand, and memory and lifetime management on the other hand

How are they separate? Like, that’s what Rust does pretty explicitly, with great results.


I'm not following, what is Rust doing exactly? Coupling schema / layout with lifetime management? If that's what you mean I would like to disagree about the "great results" because of a gut feeling, and possibly the disagreement could in theory be justified with build times, or viewpoints on maintainability or whatever. But unfortunately I have no basis for doing so. I don't understand Rust well. And have very little experience, expect failing at compiling some projects and their 500 dependencies a couple times...


Use correctly std::unique_ptr<T> has no measurable impact on performance compared with the equivalent non-smart-pointer code. You use std::unique_ptr<T> to indicate ownership, and pass raw pointers around to indicate non-ownership. That approach has the strong smell of a good programmer using the right tool for the job, especially considering the job is to communicate intent to the future reader.

It's like the classic argument against using exceptions: compared with the traditional C method of completely ignoring error conditions and not checking status, they're much slower.


> Use correctly std::unique_ptr<T> has no measurable impact on performance compared with the equivalent non-smart-pointer code.

One wart of unique_ptr (and other smart pointers) is that it cannot be passed in a register when used as a function parameter, at least with the System V ABI used on Linux.

Also, the caller is responsible for destruction and there is no way to specify that a function always "consumes" a unique_ptr so the compiler cannot eliminate the destructor code: https://godbolt.org/z/sz79GoETv

Of course if the compiler can inline the call or at least controls both and can clone the function with a custom calling convention then that doesn't have to be a problem. But it still sucks that even something as seemingly simple as a tiny wrapper around a pointer does come with a cost.


That's the point. As a rule of thumb, fine-grained ownership is a very bad idea. It makes your program into a mess, which will be slow and make your program hard to understand. The slow part applies in any case, whether you have to suffer it in code (as you do with C) or not (as in many other languages that allow you to make even more of a mess).

As a C programmer, I try to avoid tracking ownership in separate struct member fields. I try to make central data structures that keep care of the tracking. Cleaning up shouldn't happen pointer-by-pointer. Usually a much bigger context has a shared lifetime, so there is no point in splitting stuff up in individually tracked "objects". Instead you just track a bigger block of memory.


Are the answers to this stackoverflow question incorrect? https://stackoverflow.com/questions/58339165/why-can-a-t-be-...


> unique_ptr is pretty bad for performance as well.

Do you mean in terms of cache locality because it's heap-allocated instead of stack-allocated, or are you actually commenting on the overhead of copying some extra ints and invoking the destructor?

Because it's certainly correct that just because you can use a unique_ptr, doesn't mean you should. ("A std::unique_ptr is used for expressing transfer of ownership. If you never pass ownership elsewhere, the std::unique_ptr abstraction is rarely necessary or appropriate." - https://abseil.io/tips/187)


Safety is a good reason. I like protection against leaks and use after free. If I’m already allocating I’m not going to worry about the little bit of extra performance cost the abstraction might have.


To be clear: I'm not advocating for the use of `new` / `delete` over unique_ptr. But if you're creating a local object that never has to leave the current scope (or a member variable that's never moved in or out), there's no benefit to using a unique_ptr instead of creating the object directly on the stack or inline as part of your class, where the object's lifetime is bound to the scope, and your destructor is automatically run when its containing scope is cleaned up.

As an added bonus, you don't actually have to do a separate heap allocation.


I agree! You should use a regular object if possible, I’d never suggest otherwise. The rare exceptions I’ve run into are annoying initialization order issues (usually code that I didn’t have the time/knowledge/political credits to refactor) and large arrays that blow the stack.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: