-
Notifications
You must be signed in to change notification settings - Fork 0
/
2014-12-23-need-value-pass-by-value.html
18 lines (18 loc) · 5.86 KB
/
2014-12-23-need-value-pass-by-value.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---
layout: default
title: Need a value? Pass by value
description: "Passing by value and by <code>const</code> reference typically mean the same thing to the caller yet may be chosen in different circumstances, depending on who you ask. Why is writing function parameters harder than it should be in C++? Instead, let's keep it simple."
tag: C++
---
<p>C++ experts have a tendency to over-complicate the language's best practices through rigorous academic discussions about the optimality of every possible line of code. This makes it difficult to learn C++ because the rules are complicated and a consensus is difficult to find.</p>
<p>One particular case that I have come across a lot recently is the choice of whether to take a parameter by value or by <code>const</code> reference. These can both used for the same purpose — to get the value of the passed argument. After all, either will except any object of the correct type (whether temporary or not). For a long time, passing by <code>const</code> reference was popular for anything that wasn't a primitive type, to save from expensive copies. Since C++11 and the introduction of move semantics, the commonly accepted practice is to pass by value only when your function is going to need a copy anyway — allowing the move constructor to be used when the caller passes a temporary object and allowing the function to use <code>std::move</code> internally where appropriate — and pass by <code>const</code> reference otherwise. More recently, <a href="https://www.youtube.com/watch?v=xnqTKD8uD64">Herb Sutter gave evidence</a> that using a <code>const</code> reference might actually be more optimal in certain situations. I couldn't blame a person for being confused by all these different ideas about how to achieve optimal code.</p>
<p>However, there are a few problems with these approaches:</p>
<ul>
<li>We now have two things, value types and <code>const</code> references, that say the same thing to the caller: I need the value of your object. The choice of which is used often depends on what the function does to that value internally, thereby leaking internal details that the caller shouldn't need to care about. A new C++ programmer has to know this to avoid confusion and when to use each approach. A beginner shouldn't have to know the details of move semantics and copy optimizations just to write some function parameters.</li>
<li>A <code>const</code> reference doesn't succintly express its intention. It gets access to the caller's object and only transitively access to that object's value — it gets more information than is required. A <code>const</code> reference <em>could</em> mean something else. Is it going to keep this reference? Do you need to ensure an extended lifetime of your object? This isn't clear from the parameter types alone. Passing by value, on the other hand, is very expressive — it cares only about the value provided.</li>
<li>These approaches attempt to optimize before we even know that we need to — this is the very definition of premature optimization, which we all know is the <a href="http://c2.com/cgi/wiki?PrematureOptimization">"root of all evil"</a>. Most copies have minimal costs which are often mitigated by copy elision and move semantics anyway. Unless you're copying very frequently in a time or space critical part of your program, do you really need to complicate your interfaces for the sake of unnecessary optimizations? For the most part, optimization should be the compilers job — the programmer should only step in when necessary.</li>
</ul>
<p>For these reasons, I consider a <code>const</code> reference argument being used for the sake of optimization a <i>messy optimization</i>. It's an optimization that trades away the simplicity and readability of your interfaces, so it had better be worth it. Simplicity and readability come first — optimize later when you measure that there's a performance problem.</p>
<p>So what if you do measure that there's a problem? Are you copying objects around that don't really need to be copied? Are those copies expensive? If so, I don't think that <code>const</code> references should be your first port of call. First try something like the <a href="http://en.wikipedia.org/wiki/Copy-on-write">copy-on-write</a> mechanism, in which the internals of an object are only copied when written to. This way, an expensive copy is only performed when necessary and your function interfaces remain exactly the same (the copy-on-write is hidden from the user). If copy-on-write doesn't help — and <a href="http://www.gotw.ca/publications/optimizations.htm">there is evidence</a> that it might lead to bad performance when implemented for multi-threaded environments — then try something else. Sure, <code>const</code> references might be appropriate at this point. However, you should only use them when necessary, isolate them to a specific region of your code, and document their use. A reference parameter intrudes on the caller's space. I don't want to have to wonder why your function wants a reference to my object — just tell me.</p>
<p>This effectively makes the only use of <code>const</code> references when we actually require immutable access to the caller's object. That is, when the the caller's object itself, not only its value, is important to us. In these cases, we typically want to track changes to the value of that particular object. Alternatively, we might want access to an object that we really can't copy, perhaps because copying doesn't make sense for that type or you're working in a very low memory environment. These situations occur significantly less often than we use <code>const</code> references today.</p>
<p>So let's keep it simple: <b>Need a value? Pass by value.</b> It's a rule that is easy to understand, simple to teach, and gives us safe interfaces and readable code. Messy optimizations should only be used when necessary.</p>