
Pass-by-value vs. pass-by-reference-to-const in C++ - vgasparyan
https://medium.com/@vgasparyan1995/pass-by-value-vs-pass-by-reference-to-const-c-f8944171e3ce
======
jeremysalwen
I disagree with this article. There is a case where you might want to pass a
const reference to a shared pointer.

Suppose you have a class

    
    
      class Foo {
        shared_ptr<Widget> widget;
        bool has_widget();
        void set_widget(shared_ptr<widget> widget);
      }
    

and you are writing a function which may call set_widget(), it can save an
unnecessary shared_ptr copy if you pass it as a const reference:

    
    
      void maybe_set_widget( Foo& foo, const shared_ptr<Widget>& widget) {
        if(!foo.has_widget()) {
           foo.set_widget(widget); 
        }
      }
    
    

Doing a quick google search finds that Herb Sutter agrees with me
([https://herbsutter.com/2013/06/05/gotw-91-solution-smart-
poi...](https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-
parameters/)):

>Use a const shared_ptr& as a parameter only if you’re not sure whether or not
you’ll take a copy and share ownership

~~~
lbrandy
This is the only good use of a const& shared_ptr which is when you 1) only
_might_ want to claim shared ownership of the thing, and 2) the perf of those
unnecessary refcounts matter.

However, you missed a _super_ important caveat of this which is very subtle
and very awful. And that is all of the code being executing here between the
start of the function and the copy of the shared_ptr is doing it without the
protection of the reference count on the shared pointer. It's entirely
conceivable that, for example, the `has_widget` function, while executing,
invalidates the original shared_ptr passed in and thus makes your eventual use
of that shared_ptr invalid.

~~~
PaulDavisThe1st
How would it invalidate it?

~~~
ridiculous_fish
Simple example:

    
    
        shared_ptr<int> global;
        void func(const shared_ptr<int>& v) {
            global.reset();
            *v += 1; // ohno
        }
        func(global);

~~~
PaulDavisThe1st
fair enough, but that's fairly stupid programming :)

/* XXX the argument might be the same as the global value we're about to
modify. API designers, eh? */

~~~
MaulingMonkey
Doesn't have to be global - could be a member that gets modified in a
callback, or that goes out of scope because something else gets deconstructed.

I'd also like to inquire as to what nontrivial codebase you've seen that
contains no fairly stupid programming. I keep hearing about them, and trying
to prove their existence, but so far I've collected more concrete evidence for
the existence of bigfoot than I have for the existence of such codebases.

------
pianoben
I spent a small amount of time at Microsoft working in the legacy Office
codebase. It is kind of a parallel universe to normal C++ - lots of ideas in
it predate their STL equivalents, and some have yet to make it to the outside
world. Some ideas just went off in different directions. Shared pointers are
one of the latter. I no longer recall the exact name of the type, but Office
shared pointers are intrusive - your interface inherits from
`IMsoSharedRef<IMyInterface>` or something like that, and your concrete types
would in turn inherit `SharedRef` or whatever.

There are a number of really interesting aspects to this family of types, but
the one that's relevant here is that it handles situations like this very
gracefully. If you want to pass your shared object to a function that won't
take ownership (shared or otherwise), it's simple - the type just decays into
a raw pointer. So your function can be

    
    
        DoSomethingWithFoo(IFoo* theFoo);
    

and you can call it with your smart pointer

    
    
       ISharedRef<IFoo> myFoo = Mso::Make<IFoo>(...);
       DoSomethingWithFoo(myFoo);
    

This is of course a double-edged sword because you have to know that your
callee won't try to hang on to that pointer. Even so, I never once in all of
my (very strict) code reviews heard anything about the proper way to pass
pointers. (and believe me, it's the kind of codebase where you _will_ hear
about that kind of thing).

It's been a few years so maybe some details are wrong, but the gist of it is
correct.

~~~
pianoben
Oh hey, I just discovered that MS open-sourced the lowest levels of the Office
codebase, including the stuff I talked about. The most common shared pointer
type is actually the inscrutibly-named `TCntPtr`, but the file that covers
most of what I talked about above is
[https://github.com/microsoft/Mso/blob/master/libs/object/inc...](https://github.com/microsoft/Mso/blob/master/libs/object/include/object/refCountedObject.h).

Neat! ...not that I'd ever use it, or recommend its use. Its tradeoffs make
perfect sense for Office but probably not for general-purpose applications.

~~~
pianoben
The thing I was thinking of when I said "some ideas just went off in a
different direction from the STL" is called a Swarm, and it's here:
[https://github.com/microsoft/Mso/blob/master/libs/object/inc...](https://github.com/microsoft/Mso/blob/master/libs/object/include/object/swarm.h)

~~~
saagarjha
Sounds a bit like a nested autorelease pool?

------
nayuki
The author wrote in the article:

> Const correctness is a beautiful concept and quite unique to C++ (some other
> languages tried, but nothing). It serves as a contract between those who
> define the interface and those who use it.

To which I disagree. Rust also has the concept of const correctness because it
is heavily inspired by C++. Moreover, Rust's constness behaves far better than
C++. To be specific:

* In Rust, variables are immutable (const) by default, declared by "let". To make a variable mutable, you need to write "let mut", which takes more effort. By contrast, C/C++ variables are mutable by default, but made immutable by adding "const". So in Rust, the safer behavior takes less code.

* The Rust compiler checks for mutable variables that don't need to be mutable (e.g. never reassigned, never calling any mutable method), and gives you warning messages to nudge you to remove the unnecessary "mut". I don't recall C/C++ compilers offering this help.

~~~
simion314
I am not saying you are wrong because I do not know Rust but it appears you
only consider variables in your case and not cont parameters for methods and
cont methods. The const keyword is used for different things in c++ not like
let in other languages that is used only for variable declarations. I
apologize if I misunderstood your point.

~~~
nayuki
Good point - by dwelling on variables, I failed respond to the article on the
precise topic. Now I'll illustrate const methods and parameters as per your
suggestion.

Rust:

    
    
      fn reads_myself(&self) { ... }
      fn writes_myself(&mut self) { ... }
      
      fn reads_arg(w: &Widget) { ... }
      fn writes_arg(w: &mut Widget) { ... }
    

C++:

    
    
      void readsMyself() const { ... }
      void writesMyself() { ... }
      
      void readsArg(const Widget &w) { ... }
      void writesArg(Widget &w) { ... }

------
cgrealy
The code smell here is the use of shared_ptr.

Yes, there are times when it's the least bad option, but using a shared_ptr is
the C++ equivalent of throwing your hands up and going "screw it, I have no
idea who's supposed to own this".

~~~
thedance
^^ Yep. shared_ptr is almost perfectly useless. As a function parameter the
smell intensifies.

~~~
saagarjha
shared_ptr is not useless, but it is often ill-fitted to the task and
overused.

------
bradford
I know that it's a bit dated with respect to modern c++, and it's also a
partial rant, but when coming across such articles I get a chuckle from
yosefk's C++ FQA:

What is "const
correctness"?([http://yosefk.com/c++fqa/const.html#fqa-18.1](http://yosefk.com/c++fqa/const.html#fqa-18.1))

Should I try to get things const correct "sooner" or "later"?
([http://yosefk.com/c++fqa/const.html#fqa-18.3](http://yosefk.com/c++fqa/const.html#fqa-18.3))

What does "const Fred& x" mean?
([http://yosefk.com/c++fqa/const.html#fqa-18.6](http://yosefk.com/c++fqa/const.html#fqa-18.6))

Does "Fred& const x" make any
sense?([http://yosefk.com/c++fqa/const.html#fqa-18.7](http://yosefk.com/c++fqa/const.html#fqa-18.7))

What does "Fred const& x"
mean?([http://yosefk.com/c++fqa/const.html#fqa-18.8](http://yosefk.com/c++fqa/const.html#fqa-18.8))

What's the relationship between a return-by-reference and a const member
function?([http://yosefk.com/c++fqa/const.html#fqa-18.11](http://yosefk.com/c++fqa/const.html#fqa-18.11))

------
Gupie
I cannot think why you would want to a shared pointer argument instead of a
reference. A better interface would be:

    
    
       void foo( const Widget& widget );
    

Called by

    
    
       foo( *widget_ptr );
    

or

    
    
        foo( widget );
    

No doubt there are special circumstance by normally foo would not care, should
not care, whether the Widget is on the stack or heap, controlled by a dump or
smart pointer.

~~~
wffurr
That's not const-equivalent to the example. The Widget itself is mutable.

------
bminor13
I have some nitpicks with the advice given in the table:

> Q1: Yes Q2: - Q3: No Argument: `Widget& widget`

...or `Widget* widget`, which has drawbacks but makes it more clear at the
callsite that the argument passed may be modified. The Google style guide
prefers this for this reason:
[https://google.github.io/styleguide/cppguide.html#Reference_...](https://google.github.io/styleguide/cppguide.html#Reference_Arguments)

> Q1: No Q2: No Q3: - Argument: `Widget widget`

Q2 IMO should have an addendum to read "Is it expensive to copy a Widget _and
if not, will it always be cheap_ " because there is nothing to stop someone
from adding to Widget until it crosses the threshold of "expensive", at which
point someone needs to go through and update all the callsites to pass by
const ref. Widget's copy cost really needs to be part of its API contract
before pass-by-value can be allowed.

~~~
PaulDavisThe1st
google's rationale is good but not iron-clad.

in ardour, we use the convention that if a null value is allowed, pass a
pointer; if a null value is not allowed, pass a ref.

the idea that Widget& implies something different about the likelihood of
modification compared with Widget* is quite foreign to me. this is precisely
what Widget const & is for.

~~~
bminor13
> the idea that Widget& implies something different about the likelihood of
> modification compared with Widget* is quite foreign to me. this is precisely
> what Widget const & is for.

The issue is not ambiguity in the declaration, but rather the callsite: it's
rarely possible to tell if a parameter is passed as a value, ref, or const ref
at a particular callsite without consulting the function declaration.
Oftentimes, one reads code without comparing each function call to its
declaration, so it's easy to miss instances where objects are passed around by
mutable ref rather than the more common const ref.

Using a pointer for the mutable cases resolves this ambiguity by forcing
different syntax to be used at the callsite, which reminds:

* code authors that the parameter they're passing can be modified by the function call (they should think carefully about whether this is desired)

* code readers that a function call is potentially modifying its parameter (which can aid in debugging)

~~~
PaulDavisThe1st
That works if the call stack is only ever shallow (specifically, the owner is
always the caller).

But the moment you've called

    
    
       hello_i_accept_pointers (&myvar)
    

you're now inside a callstack where the variable is a pointer, and there's no
syntax to indicate that you're passing a pointer to the next callee.

~~~
bminor13
That's true; inside `hello_i_accept_pointers` there's no syntax at callsites
to indicate that a mutable pointer is being passed. However, the variable
being passed is of pointer type, which is readily apparent from the context of
the function - the pointer parameter is declared in the same file, likely only
lines above the callsite in question. This is still better than having to find
the declaration of the called function, which is probably in a different file
altogether.

~~~
PaulDavisThe1st
what are these files that you speak of ? :))

seriously, even with trusty old emacs and ag(1), the declaration of the called
function is a fraction of a second away, no matter where it lives.

~~~
d1zzy
But having it obvious at the call site means I don't even have to do that,
dozens of times per day, every day.

It's something that's not easy to realize how useful it is until you actually
do it. When working in a large codebase that consistently follows those rules
it just makes code reading a breeze, it severely reduces the number of
"context switches".

Obviously it's not the only way it can be done but it is a way and the
benefits are significant.

------
usefulcat
I disagree with this advice; I pass shared_ptr by const ref all the time to
avoid lots of useless ref count updates.

I mean, I guess it's _a_ way to avoid this particular problem
([https://godbolt.org/z/8g6HJg](https://godbolt.org/z/8g6HJg)), but even
without passing shared_ptr by const ref that code still has a far worse code
smell, namely the way it mixes manual and automated memory management. After
all, the whole point of creating a shared_ptr instance is to take (possibly
shared) ownership of a thing.

~~~
codeflo
You limit the generality of the function by passing it more than it needs. If
your function doesn’t care about the shared_ptr itself, then you should only
be passing a const ref to the value (which you get by dereferencing the
shared_ptr at the call site). That way, your function works with values in
shared_ptrs, unique_ptrs, values on the stack, manually managed pointers etc.,
without you doing any extra work.

~~~
usefulcat
This comment is exactly right, and is what I do whenever possible. However
there are cases where a function may or may not need to copy a shared_pointer
that was passed to it as an argument. In such cases, the extra refcount
updates that would be incurred for passing by value are not useful but still
incur a cost. So my usual practice is to copy a shared_ptr only at the point
where ownership needs to be shared.

------
butterisgood
This doesn’t address moves at all? I might need to read it again but I don’t
think it’s as easy as the author claims it is.

~~~
zeotroph
Indeed, not linking this discussion to `std::move` which is half the reason to
do the copying at the callsite is a big oversight.

And not pointing out that a `foo(const std::shared_ptr<Widget>& widget);`
should most likely become a plain `foo(const Widget& widget);` with a
`*widget_ptr` dereference at the callsite.

~~~
afc
But what if I _sometimes_ want to retain shared ownership? I don't want to
receive the shared_ptr by value because that drastically slows down my program
for the cases where I end up not retaining ownership (compared to const-ref),
but I can't just take the const ref Widget.

~~~
zeotroph
Hence _most likely_. But when you are throwing around shared pointers left and
right the program is usually already in the phase of "I don't know... just
stick into a shared_ptr to be safe".

~~~
butterisgood
And that’s my concern too. Shared pointers aren’t free.

You can solve problems with them but the moment I see them in code it raises
flags that make me think someone gave up solving the actual problem at hand
and “this seemed to work”.

I’ve seen many an otherwise good looking code base paint itself into corners
over object ownership, life cycles and resource reclamation.

------
mistrial9
from a high level overview, this seems to echo the design tension between perl
and python ; "there is one way to do it" yes or no! The creativity of perl
with its (almost) poetic sensibility (and many synonymous expression forms)
versus a "just the facts" python science culture..

In the case of Modern C++, the heavy emphasis on performance plus syntax
density, take away the fun parts (to me) and leave one with neither fun
synonyms nor just-the-facts simplicity!

Isn't this sort of hair-splitting one reason Theology has a bad name amongst
some intellectuals ?

~~~
taeric
This feels like propaganda for python. :)

~~~
throwaway17_17
I feel like the description of Perl vs the description of Python makes Perl
seem the more lover of the two.

------
mark-r
Is there a way to copy from shared_ptr<Widget> to shared_ptr<const Widget>? I
think not, which would make one of the entries in that table completely
impossible to use.

~~~
rq1
const_pointer_cast should do the job.

~~~
gpderetta
That's for the reverse (unsafe) conversion. A shared ptr can always be
implicitly converted to a shared ptr to const. In fact a shared_ptr<T> can
always be implicitly converted to a shared_ptr<T2> as long as T* is
convertible to T2*. So for example derived to base and anything to void are
allowed.

~~~
mark-r
Do they then share the reference count, or can you get in trouble by going out
of scope in the wrong order?

~~~
gpderetta
They will share the same reference count. In fact you can make two shared_ptrs
to arbitrary types share the same reference count (although in this case there
is no implicit conversion and you need to use the explicit aliasing
constructor); it is useful for interior pointers for example.

------
asveikau
The copy constructor of shared_ptr is expensive. It's most likely going to do
atomic ops on the reference count. The destructor will do the same and add
branching. The author talks as if copying and destroying shared_ptr is cheap,
analogous to copying a pointer. It is not.

------
brian_herman__
This is why I'm not a C++ programmer, whenever I look at C++ I am amazed by
how complex it is.

~~~
StillBored
I've tended to like C++ as a user-space programming language, but I also tend
to agree that its completely jumped the shark in the past decade. The current
trope of "modern C++" programmers seem like the modern equivalent of the "pure
OOP" crowd that infected C++ in the late 1990 early 2000's. The difference
being that the modern C++ people have created what is pretty much worse in
class syntax messes as they have tried to bolt concepts on using the template
syntax. The shared_ptr<>/make_unique<> etc syntax is mostly nonsensical to
anyone who isn't willing to buy into the cult. I'm really wishing those people
had forked the language into C+++ or something so they could have actually
deprecated all the syntax they hate and replaced it with what they believe to
be the one true path, leaving the core C++ language mostly static, and fixing
only the most critical of issues (as is C).

~~~
userbinator
_The current trope of "modern C++" programmers seem like the modern equivalent
of the "pure OOP" crowd that infected C++ in the late 1990 early 2000's._

Precisely. Whereas the latter debate and complain about whether code is "OOP
or not", and think non-OOP is a "bad practice", now we have the same
contention around whether code is "pure C++" and dogma that "C with classes"
is to be avoided. Hopefully the latter group will also eventually realise the
silliness of it all.

I personally use C++ as "C with some extra features" and try to avoid most of
the template insanity.

------
rq1
The No-No-Whatever case should be ˋconst Widget` or did I miss something?

Passing a shared_ptr by const ref is okay if you’re absolutely sure about your
(smart) pointer lifetime. I’m never really sure or how things will evolve...
Yesterday is history, tomorrow a mystery!

That’s why rust’s lifetimes rock!

------
29athrowaway
I try as much as I can to use r-value references and move semantics.

------
codr7
Isn't it about time we let go of the distinction? I get that C++ is a lost
battle, but moving forward.

What I'm really interested in is whether an argument is allowed to be modified
by the function or not. I'm pretty sure the compiler has a better chance of
making the right decisions concerning performance. Especially considering how
best practices morph as languages/compilers evolve.

Go has the same issues, I'm at the point where I'm mostly passing anything
that's not supposed to be modified by value. Notable exceptions are
interfaces, which need pointer receivers to work for reasons.

~~~
saagarjha
This is about semantics, not performance. It’s similar to access control in a
way.

~~~
codr7
Barely, the only reason co-owning a shared-pointer for a little while is a
problem is because of performance problems caused by locking.

