Paavo Helde <
myfir...@osa.pri.ee> writes:
> On 18.07.2018 13:42, Tim Rentsch wrote:
>
>> Reference counting also has several
>> disadvantageous properties: it will not reclaim cyclic structures
>> unless they explicitly have the cycles removed;
>
> Using std::weak_ptr might provide some mitigation here.
Sure. But that starts to move into the domain of doing manual
management. One needs to think about where to use the weak
pointers: not enough, and cycles don't get reclaimed; too many,
and the data structure falls apart too early. That doesn't mean
reference counting is hopeless -- any non-trivial application does
some level of manual management, and even full-scale mark/sweep
environments need weak pointers in some circumstances. The
important thing is that making sure everything gets reclaimed
incurs a mental cost, and that cost is higher for reference
counting than it is for mark/sweep-style collectors.
>> it consumes more
>> resources (as a general rule) than more global schemes, in both
>> space and time;
>
> Depends on the smartpointer. For example, std::unique_ptr consumes
> zero resources in both space and time. Yes, std::shared_ptr is a bit
> heavyweight because the reference-count update is required to be
> multithread-safe. A single-thread only smartpointer is much faster in
> multithreaded programs, but its usage obviously requires more care.
(Disclaimer: I'm not a unique_ptr expert. I did look into the
issues here, and believe my comments below are correct, but I very
well may have missed or misunderstood something. All feedback
welcome.)
First, my comment was about reference counting, and unique_ptr is
not reference counting. Or it may be thought of as 1-bit RC -- it
either has a non-null pointer or it doesn't (and the "count" is in
the unique_pointer itself, not in shared per-object memory). In
any case it is definitely less automatic than full RC (which I take
as being equivalent to shared_ptr, with a similar disclaimer). As
such it requires more mental effort than using shared_ptr or some
other more complete RC scheme.
Second, regarding resource use. The idea that unique_ptr has no
space or time overhead is, I believe, incorrect. In particular,
when updating a unique_ptr from a different unique_ptr (temporary),
there is a space cost in the form of code space needed for a value
check, and a time cost in checking the value to see if a destructor
needs to be called. Those may not be large costs, but they are not
zero. Compare to a mark/sweep-style environment, where all that
needs to happen is copying the pointer.
Third, besides needing more manual management, using unique_ptrs
carries an additional mental cost in having to understand the more
elaborate language semantics that they rely on. In particular,
unique_ptr has move semantics but not copy semantics. The kinds of
code patterns used for "normal" objects (ie, that do have copy
semantics) in many cases simply don't apply to unique_ptrs. To use
unique_ptrs requires a new kind of thinking, which means more mental
cost. I'm not saying these things can't be learned -- obviously
they can be. But there is an initial cost in learning, and more
importantly an ongoing cost in using (cost here meaning mental
effort) things like unique_ptr that don't use the usual semantic
constructs. If that use could be tucked away underneath a layer of
abstraction that would be one thing, but with unique_ptr it's right
there in your face and there's no getting around having to deal with
it. Is that a good tradeoff to make? I expect in some cases it is.
I believe though that in many cases a different tradeoff would be
better.
>> the invariants maintained must be kept exactly
>> right, which makes it reliant on destructors being run, which may
>> lead to hard-to-understand behavior in the presence of exception
>> processing.
>
> Not sure what you want to say here? [...]
Invariants maintained locally and incrementally are often harder
to get right and harder to understand than invariants maintained
centrally.
>> Probably the biggest upside of GC-style memory management is a
>> big increase in productivity, which has been measured as a
>> factor somewhere between 1.5 and 2.
>
> Compared to what?
Compared to doing all memory management manually.
> To the proper C++ code using std::vector,
> std::string, std::make_unique, std::make_shared, or to the C or
> C-style C++ code calling malloc/free or new/delete manually?
Even just using unique_ptr and shared_ptr, C++ provides a form of
reference counting, which is more automatic management than having
to manage all memory (de-)allocation manually. I don't know of any
studies that compare the productivity results of using reference
counting versus a more centralized form of garbage collection such
as mark/sweep. Based on personal experience I expect that some
level of increased productivity would be found, but it would be
nice to get some more objective results on that.
> I'm sure GC suits fine some programs.
Yes, and the question is which programs (and which classes of
programs). I would never claim that any approach is right for all
applications. But I think many people, or maybe even most people,
form opinions about that based on pre-conceptions that turn out
not to be right if examined more closely, so I like to encourage
people to look at things from different points of view, and see
if their perceptions might change on further investigations.