Hacker News new | past | comments | ask | show | jobs | submit login
Pass-by-value vs. pass-by-reference-to-const in C++ (medium.com)
84 points by vgasparyan 7 days ago | hide | past | web | favorite | 87 comments





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...):

>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


This is not untrue, but the counterpoint would be that the intersection of a) the number of situations where the shared pointer copy would have any measurable influence on performance, and b) using shared pointers is a good idea for that particular problem in the first place, is likely vanishingly small.

The main performance ‘problem’ with shared pointers is locking and contention in heavily multithreaded scenarios, which is why pretty much nobody uses a reference counting memory model for these kinds of things. In the vast majority of single or lightly threaded scenarios I think you will be hard pressed to find any reasonable algorithm implemented using shared pointers where the reference counting overhead is even measurable. So it’s not a strange thing at all to deprioritize hypothetical performance gains over clear intent of your code. Best thing is to stay away from shared pointers unless you really need them anyway, as they promote designs with sloppy ownership semantics. Been there, done that, try to get rid of them years later... :-/

The almost obligatory Herb Sutter reference made me chuckle by the way. He says so many things about what he thinks is the right thing to do in C++, and gets quoted almost as an absolute authority on good C++ practices, but quite often (not always, mind you) I find his arguments weak and unconvincing, and his recommendations sometimes downright counterproductive.


> Best thing is to stay away from shared pointers unless you really need them anyway, as they promote designs with sloppy ownership semantics.

Could you elaborate? I'm not an expert in C++ but smart pointers did simplify a lot of my code and I never met the case where it was a problem.

Like everything else in C++ (and any low-level language), they should be used carefully.


With shared pointers it is very hard to reason at which point things get deleted, from just looking at the code. This can become particularly messy when you communicate them freely across API boundaries, because that allows callers to just store the shared pointers they get from your API instead of e.g. making private copies or not keeping references at all. Especially in large code bases worked on by multiple people, object lifetime will grow beyond anyone’s comprehension, and people writing new code on top of the existing code will have little other choice than just default to copying around the shared pointers everywhere.

If you don’t know what the lifetime of your objects is, it is very difficult to follow RAII best practices, to control memory use (a shared pointer that is held on to where it is not necessary is basically a leak), to avoid high and/or unpredictable allocation/deallocation cost (compared to e.g. arena allocators or just stack copies), etc. On top of that there is a very conceivable risk of introducing cycles (and hence leaks) when overusing shared pointers. And last but not least debugging code that defaults to shared pointers everywhere can be a nightmare.

These are all practical downsides, but to me personally the argument that you have to be explicit about your memory model (allocation, ownership, lifetime) and use the right tools for your design is even more important. Shared pointers are definitely a very useful tool, but only in situations where you truly want to have shared ownership, and not just because they are convenient because they save you from having to think about these things. Over time I’ve found that more often than not, using strictly unique_ptr for ownership, raw pointers and references to avoid copies, and plain value copies where appropriate and not performance critical, leads to code that is much easier to reason about, has less bugs, etc.


Indeed. Practical example from another language. I was using a thread pool written by someone else to execute some tasks. For reasons, the tasks were essentially represented as shared_ptr's to an object instance.

The pool dequeued a task, issued it to a thread, the thread then called a method on the object and asked for more work.

I stumbled over a case where the destructor of the task was not always called, leaving all sorts of handles hanging.

Turned out the code that dequeued a task did so into a local variable, and didn't clear that variable after it had passed it on to the thread. So when the pool ran out of work, a reference to the last task was being held by the local variable in the pool code, and that reference was only released when new work arrived or the pool was destroyed.


In my experience unique pointers are usually a better fit than shared pointers. That is not to say shared pointers do not have their place, but they can definitely be abused. Especially for developers that come from garbage collected languages, it might be tempting to replace all pointers with shared pointers and call it a day. Not only can this cause performance issues when these are used in performance critical locations, but you can also lose track of who owns what very easily.

It is also very easy to accidentally create cyclic references with shared pointers. Suppose you have a class like this:

   struct Node {
      shared_ptr<Node> parent;
      shared_ptr<Node> child;
   };
This rather innocent looking class creates a cycle when you fill in the values, because the parent and child reference each other. That cycle now means your nodes will not be cleaned up anymore.

Unique pointers do not have any of these problems. They cannot create cycles, are very cheap and they make it very explicit who owns what. They are not suitable for every situation (i.e. when there is actually shared ownership shared pointers should be used) but in most situations I find that they are sufficient and have a lot less pitfalls than shared pointers.


> They cannot create cycles,

I don't think this is true. Couldn't you accidentally make two objects "own" each other?


Yes, excellent point. It is possible to make cycles, it is just a lot more difficult to do accidentally than with shared pointers.

> This rather innocent looking class creates a cycle when you fill in the values, because the parent and child reference each other. That cycle now means your nodes will not be cleaned up anymore.

It’s fairly common knowledge, but just to answer the inevitable question: you can avoid the cycle with weak pointers.


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.


I would assume the caller itself is holding on to the shared pointer.

True but that's bug-for-bug with passing a const&

How would it invalidate it?

Simple example:

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

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? */


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.


You would need to pass it across threads. At least that is all I can come up with.

that will not invalidate it. it might have other (negative) implications, but it does not invalidate it.

It doesn't automatically invalidate it, but it makes possible things that would invalidate it.

such as?

Other responses (different replies to grandparent) have examples.

More realisticly, if you pass a reference to a different thread and return there is no way when to know when the original goes out of scope. Don't do that.


the whole point of using shared_ptr<T> is to remove the need to "know when the original goes out of scope".

you don't need threads for what you're describing: simply passing it to a same-thread method/function which creates a heap-based copy will already complicate your understanding of the lifetime of the referenced object.

concrete example: we make wide use of the so-called "signals&slots" pattern, which is more accurately denoted as "anonymous callbacks". we have had a consistent source of serious lifetime mgmt problems caused by (boost|sigc|std)::bind()-ing a shared_ptr<T> into the argument list for the callback - the referenced object now lives for as long as the callback itself lives. This isn't explicit in the code, and even today, probably 12-15 years after we first realized the anti-pattern, we still do it from time to time.


Thank you for writing that. I was about to write ... exactly the same. There is a very legitimate use case for passing the const reference to the shared pointer that the article doesn't consider at all, which you've explained very well.

I want a new term - "author smell" or "article smell".

I loathe when someone or some article rants on about their silver bullet solution, or alternatively, that doing some thing is always bad.

I learned a test taking strategy early in my life - when faced with a multiple choice question, if you see "always" or "never", that answer is usually incorrect.

To come back around to this article and similar software development articles - we have many tools in our toolbox, the trick is knowing when to use (or not use) a particular tool. Never throw your tools away.


There’s a few exceptions to your rule, too ;) Never use std::auto_ptr, for example.

I think another way to put it is that when I am calling a non-async function, for instance, I am not sharing ownership of the shared object. So calling a normal function that blocks probably would be best as the Widget ref/Widget * with/without const depending on the type of operation. But the copy of a shared_ptr<T> is saying that we are sharing ownership and the cases where that applies. An async operation, like the argument to a thread or a task, probably would require sharing ownership/lifetime of the object. Or it may be a transfer of ownership via std::move.

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.


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....

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.


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...

Sounds a bit like a nested autorelease pool?

the standard library has enable_shared_from_this, an opt-in base that allows safely constructing a shared pointer from a raw pointer, implicitly enabling intrusive counting. Implicit conversion to raw pointer woudl just extremely unsafe though:

  std::shared_ptr<int> x {new int(0)};
  {
    std::unique_ptr<int> y{x}; // not actually allowed 
  }  // boom

That's true! This is one of those instances of parallel evolution. The MSO construct predates even Boost's `enable_shared_from_this`, as I recall. FWIW when I was there, new code was encouraged to use STL smart pointers where possible - people were always looking for opportunities to shed legacy patterns where we could.

I totally agree about the danger of implicit conversion to raw pointers - it breaks the whole point of smart pointers. The reason it makes sense for Office is that huge swathes of code are written in "c++99" style - no exceptions, all functions return HRESULT error codes, and most functions are very explicit about who owns what. By convention, functions accepting pointers almost (?) never hang on to those pointers. Those functions that _do_ take ownership of (or retain an interest in) a pointer argument, explicitly ask for a smart pointer.

Like I said, not really suitable for general use, but darn convenient _within the context of Office_.


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.


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.

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) { ... }

Rust treats parameters identically to local variables, since they are just local variables. All rules that apply to variables also apply to all function arguments (including `this`/`self`).

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".


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

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

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)

Should I try to get things const correct "sooner" or "later"? (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)

Does "Fred& const x" make any sense?(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)

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


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.

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

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_...

> 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.


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.


> 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)


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.

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.

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.


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.


> there is nothing to stop someone from adding to Widget until it crosses the threshold of "expensive"

Generally, things that are passed by value are de-facto “frozen” and will not have new members added to them, as they’d no longer be “zero cost”. For example, std:string::iterator is “logically” a char * and this will likely never change.


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), 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.


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.

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.

What the article skips over, but is somewhat implied, is that in the case you want to pass const std::shared_ptr<Widget>&, you're not taking ownership anymore. So you should be dereferencing (outside the func rather than in) and passing const Widget& instead, because the shared_ptr isn't useful anymore and you're "breaking" the contract of shared_ptr managing it's memory. const Widget& makes it clearer you're not owning the memory in the func. There is a caveat that is mentioned in other comments about there's still a use for const std::shared_ptr<Widget>& if you're not sure whether you'll copy the Widget to a shared_ptr in the func.

I'm pretty sure making a copy of a shared_ptr needs to modify it, so having a const reference doesn't work.

It doesn't modify anything inside the shared_ptr itself, but a refcount which is pointed to by a pointer field in the shared_ptr. So you can say

  foo(shared_ptr<T> const &x) {
    saved_x = x;
  }
and it'll update the refcount exactly once when doing the assignment.

That's the problem with modern C++, you can't reason about it on general principles - you have to know way too much about implementation detail.

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.

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.


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.

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".

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.


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 ?


This feels like propaganda for python. :)

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

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.

Why not? In fact shared_ptr even has an aliasing constructor that allows you to refer an pointer whose lifetime is tied to an unrelated shared_ptr.

Of course the only sane use case is that that unrelated shared_ptr will definitely outlive that other pointer. But you get the idea, shared_ptr is incredibly flexible.


const_pointer_cast should do the job.

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.

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

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.

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.

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

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).

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.


For this reason I went back to C and have been experimenting with Rust.

It’s a shame, because there are some places where C++ is a strict improvement over C, but you have to accept so much more baggage.


> The shared_ptr<>/make_unique<> etc syntax is mostly nonsensical to anyone who isn't willing to buy into the cult.

Could you explain how you would improve the shared_ptr syntax? I'm not sure you could make it that much simpler. std::shared_ptr<Something> seems pretty clear to me.


C++ is in some ways a job security mechanism. Careers are made on deep knowledge of the language. There are whole conferences about language details...

Better that than learning a new web framework every 6 months in perpetuity.

React was released in 2013...

And your code from 2013 pretty much certainly doesn't work anymore. Whereas C++11 is still perfectly adequate, and it works with the latest compilers.

Well as a serious C++ developer you'll probably want up-to-date and code in C++17 nowadays and that's similar to re-learning JS framweworks every few years. I guess the JavaScript world has it own "mechanisms" for job security.

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!


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

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.


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

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



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

Search: