
Writing comments about pointer ownership - nice_byte
http://gpfault.net/posts/pointer-ownership-comments.txt.html
======
merogin

      Some people might ask why I chose to make the parameter of BlackBox::store an rvalue reference rather than an lvalue reference and force the client code to use std::move. While it is true that an lvalue reference would also have worked, if I did that, it would be impossible to write code like crate.store(std::make_unique<Gadget>(beeper, flasher));. I also happen to think that forcing the client to use std::move at the call sites is good rather than bad, because it communicates the intended effect to the reader.
    
    

Passing unique_ptr by value is the correct option here, it will accept
temporaries just fine.

And in my opionion, if you want safety quarantees like that, use Rust. I was a
fan of the new C++ standards a while ago, but after having used them for some
time, I found C is a lot nicer language. There's just way too much mental
overhead when trying to write "idiomatic C++" and the compiler errors you
sometimes get are just a waste of developer time.

~~~
nice_byte
Someone brought up this point during another discussion and initially I agreed
with them. However, someone else suggested that if the function has more
parameters, an exception might happen when passing the arguments for the other
parameters, so the transfer of ownership won't happen, but the caller will
have already lost the ownership. Passing by rvalue reference means the caller
retains ownership until the very last possible moment.

~~~
merogin
Do you mean something like this?
[http://ideone.com/8tPPHy](http://ideone.com/8tPPHy)

~~~
nice_byte
Yes. It seems to work, but it might be a coincidence. The order of argument
evaluation is undefined. It might be that the exception gets thrown before the
ownership is transferred, but it's just luck. I have modified your example a
bit, to better trace what's happening:
[http://ideone.com/tgh0xe](http://ideone.com/tgh0xe)

~~~
merogin
And it won't work when switching the parameter order of byval, so you're
correct. That's quite nasty.

------
ohazi
I really wish they had found a less confusing way to bolt this move semantics
/ rvalue references stuff onto C++. It's an important addition, but I find
myself having to do mental gymnastics every time I see code like this, whereas
in Rust it just works.

~~~
jasode
_> I really wish they had found a less confusing way to bolt this move
semantics / rvalue references stuff onto C++._

There's some irreducible complexity with adding ownership semantics and the
way standards committee ended up adding it to C++ is probably the most
simplified way to accomplish it _if you obey the constraint that the new
compiler-assisted pointer ownership must coexist with legacy raw pointers and
references._

To me, the complexity lies in the 30 years of un-annotated raw pointers in
existing C++ code _that must not be broken or rewritten_ if new ownership-
style pointers are bolted on to the language.

I also think that another source of complexity/confusion is the label " _move_
" in "std::move()". The word "move" is a _verb_ and it seems to hint to
beginners that some bytes changed position but unfortunately, that's not
what's really happening. It might have been better to call it "std::rvalue()"
\-- the " _rvalue_ " is a _noun_ and you're telling the compiler that it is an
"expiring value" that you promise to stop using. (Howard Hinnant[1] is the
author of the std::move() C++ proposal and he gives good reasons why he called
it "move" but I still think it's a confusing name.)

 _> , whereas in Rust it just works._

In C++, the defaults are "raw pointer" and unannotated references; the special
cases are are unique_ptr, shared_ptr, &&, and std::move()

In Rust, the default is explicit borrow pointers, and the special-case
pointers are raw pointers inside unsafe blocks.

Rust is "simpler" because it has the luxury of using a different baseline for
its default case. C++ doesn't have that luxury.

[1][http://stackoverflow.com/a/21358433](http://stackoverflow.com/a/21358433)

~~~
andrewflnr
I think most of us understand the historical pressure, but that's small
comfort when we're neck-deep in arcane code. Indeed, for me it just leads to
dark thoughts about the reign of Worse Is Better and how the good stuff rarely
wins.

~~~
jasode
_> I think most of us understand the historical pressure,_

It doesn't look that way from the GP's comment: _" I really wish they had
found a less confusing way to bolt this move semantics / rvalue references
stuff onto C++ ..."_

... is actually an example of _not_ understanding (or not being aware of)
historical pressure.

If you create a _new_ programming language (e.g. Rust) with a blank slate, you
have more flexibility to make semantics "simpler" by rethinking the design of
the syntax, the sigils, and the implicit defaults.

Rust syntax accomplishes "move semantics" without double ampersands (&&)
sigils and "std::move()".[1] The end result is that Rust syntax looks less
cluttered to move ownership around.

The key to Rust's clean syntax is the _absence_ of "historical pressure".

Rust starts from a blank slate without worrying about breaking legacy code.
Rust wasn't invented in 1982.

C++ move semantics uses C++1982/C++98 as a starting point and _can 't_ use
naked variables to let the compiler carry out "moves". Legacy code would be
ambiguous and compilation would break. The "rvalues std:move()" was proposed
in 2002 and finalized for C++11. They had 9 years for others to challenge
Howard's proposal and devise a simpler more elegant way _that doesn 't break
existing code._ Nobody came up with an alternative. Hence, the uglier C++
syntax bolted on for "moves" is probably as simple as we're going to get.

[1][http://stackoverflow.com/questions/29490670/how-does-rust-
pr...](http://stackoverflow.com/questions/29490670/how-does-rust-provide-move-
semantics)

------
rtpg
Is this provably safe? I feel like there's been a lot of discussion in the
Rust community RE safeness, and my impression from the sidelines is that it's
non-trivial.

~~~
pcwalton
> Is this provably safe?

No.

------
pjmlp
The worse thing about comments about pointer ownership are the big teams with
lots of programmer attrition.

Eventually no one knows any longer how those comments map to the real code,
because lots of those ever changing developers never update them.

Which leads to days delving into pointer related bugs.

If one is lucky to work in a shop that allows for modern C++, then it is great
being able to use all the available features to avoid this type of situation.

------
dragontamer
On the contrary. My team is all C (I really want to move to C++ however). So I
need to leave pointer ownership comments around.

I more or less do what unique_ptr does automatically, except with comments. I
note "sources" and "sinks" of pointers... and work with them in that manner.

------
ridiculous_fish
Unfortunately this advice fails for forward references, which is one of the
most common reasons for using a pointer (e.g. the pimpl idiom). An example:

    
    
        #include <memory>
        struct Gadget;
        struct BlackBox {
            std::unique_ptr<Gadget> contents;
            ~BlackBox();
        };
        
        void func() {
            BlackBox b;
        }
    
    

This will fail to compile (for reasons I don't understand - can anyone
explain?) But replace the unique_ptr with a raw pointer and it will be fine.

~~~
cmma
The reason why that code fails is explained by gcc's unique_ptr
implementation:

    
    
        /// Primary template of default_delete, used by unique_ptr
        template<typename _Tp>
          struct default_delete
          {
        ...
            void
            operator()(_Tp* __ptr) const
            {
              static_assert(!is_void<_Tp>::value,
                            "can't delete pointer to incomplete type");
              static_assert(sizeof(_Tp)>0,
                            "can't delete pointer to incomplete type");
              delete __ptr;
            }
          };
    

Basically, invoking "operator delete" on a pointer to an incomplete type (aka
opaque pointer) is usually undefined. Calling "operator new" on an incomplete
type is not possible (because its size is 0). Since unique_ptr deals with
allocation/deallocation, and doesn't just copy pointer values around, it's not
fully comparable to a plain pointer.

nice_byte proposed a good solution: if you can, abstract the new/delete logic
to another file, in which the complete type of "Gadget" is known.

~~~
ridiculous_fish
That's true, but it's not obvious why anything in that code calls new or
delete, since the destructor is defined elsewhere. So why does the
_constructor_ invoke operator delete?

The missing puzzle piece is apparently exception handling: if the implicit
constructor throws (which of course it cannot) it must delete this object
(which of course will be null). Sadly this silly requirement persists even
when exception handling is disabled at the compiler level.

------
bluecmd
Why not use shared_ptr in this case? Why enforce single ownership?

~~~
nice_byte
Because the example is about an idiomatic way of enforcing exclusive pointer
ownership. Maybe it wasn't a very good example though. A better example might
be this: in the Builder design pattern, an object is built from various
"parts" by the client. It's obvious that the object should have an exclusive
ownership of all of its parts. However, at the time of creation, each part
belongs to whoever created it, so the ownership needs to be transferred.

------
crystalgiver
Ugh, if you bring up Rust in this thread it's clear you have little to no
experience actually writing large amounts of system-level code. Rust has been
around for all 2-3 years now? 1.0 for what? Months? What large successful Rust
code bases have you worked on? What have you actually built with Rust?

C++ is for people who work on large mature codebases, deal with practical
issues, and actually build stuff. Talk to me about the virtues of Rust when
you actually have real experience.

~~~
pcwalton
> What large successful Rust code bases have you worked on? What have you
> actually built with Rust?

> C++ is for people who work on large mature codebases, deal with practical
> issues, and actually build stuff. Talk to me about the virtues of Rust when
> you actually have real experience.

According to the GitHub stats, I've added 293,494 lines of Rust code to Servo
and 381,084 lines of code (mostly Rust) to the Rust compiler. I find that
Rust's pointer ownership rules help improve the reliability and security of my
code significantly.

~~~
crystalpsychic
Your Rust stats are irrelevant. You're literally the creator of Rust.

Outside of you, the majority of "users" of Rust are script-kiddies who think
it's "cool" (like Haskell cool) and will likely never write any native
application of any significance using it (or any low-level language for that
matter).

~~~
kibwen
Given that you seem to think that pcwalton is the creator of Rust, I'm
guessing that your professed knowledge of the demographics of the Rust
community is also entirely fantastical. Would you like to cite a source for
your ad-hominem throwaway comments?

~~~
crystalgiver
I think rust has its merits, don't get me wrong. I just think it has a large
cargo cult following of inexperienced script kids who have never used it for
anything other than pretending to know what they're taking about.

~~~
kibwen
And what makes you think that?

