Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>To understand why this is a good pattern, we should examine the alternatives, both in C++03 and in C++11.</p> <p>We have the C++03 method of taking a <code>std::string const&amp;</code>:</p> <pre><code>struct S { std::string data; S(std::string const&amp; str) : data(str) {} }; </code></pre> <p>in this case, there will <em>always</em> be a single copy performed. If you construct from a raw C string, a <code>std::string</code> will be constructed, then copied again: two allocations.</p> <p>There is the C++03 method of taking a reference to a <code>std::string</code>, then swapping it into a local <code>std::string</code>:</p> <pre><code>struct S { std::string data; S(std::string&amp; str) { std::swap(data, str); } }; </code></pre> <p>that is the C++03 version of "move semantics", and <code>swap</code> can often be optimized to be very cheap to do (much like a <code>move</code>). It also should be analyzed in context:</p> <pre><code>S tmp("foo"); // illegal std::string s("foo"); S tmp2(s); // legal </code></pre> <p>and forces you to form a non-temporary <code>std::string</code>, then discard it. (A temporary <code>std::string</code> cannot bind to a non-const reference). Only one allocation is done, however. The C++11 version would take a <code>&amp;&amp;</code> and require you to call it with <code>std::move</code>, or with a temporary: this requires that the caller <em>explicitly</em> creates a copy outside of the call, and move that copy into the function or constructor.</p> <pre><code>struct S { std::string data; S(std::string&amp;&amp; str): data(std::move(str)) {} }; </code></pre> <p>Use:</p> <pre><code>S tmp("foo"); // legal std::string s("foo"); S tmp2(std::move(s)); // legal </code></pre> <p>Next, we can do the full C++11 version, that supports both copy and <code>move</code>:</p> <pre><code>struct S { std::string data; S(std::string const&amp; str) : data(str) {} // lvalue const, copy S(std::string &amp;&amp; str) : data(std::move(str)) {} // rvalue, move }; </code></pre> <p>We can then examine how this is used:</p> <pre><code>S tmp( "foo" ); // a temporary `std::string` is created, then moved into tmp.data std::string bar("bar"); // bar is created S tmp2( bar ); // bar is copied into tmp.data std::string bar2("bar2"); // bar2 is created S tmp3( std::move(bar2) ); // bar2 is moved into tmp.data </code></pre> <p>It is pretty clear that this 2 overload technique is at least as efficient, if not more so, than the above two C++03 styles. I'll dub this 2-overload version the "most optimal" version.</p> <p>Now, we'll examine the take-by-copy version:</p> <pre><code>struct S2 { std::string data; S2( std::string arg ):data(std::move(x)) {} }; </code></pre> <p>in each of those scenarios:</p> <pre><code>S2 tmp( "foo" ); // a temporary `std::string` is created, moved into arg, then moved into S2::data std::string bar("bar"); // bar is created S2 tmp2( bar ); // bar is copied into arg, then moved into S2::data std::string bar2("bar2"); // bar2 is created S2 tmp3( std::move(bar2) ); // bar2 is moved into arg, then moved into S2::data </code></pre> <p>If you compare this side-by-side with the "most optimal" version, we do exactly one additional <code>move</code>! Not once do we do an extra <code>copy</code>.</p> <p>So if we assume that <code>move</code> is cheap, this version gets us nearly the same performance as the most-optimal version, but 2 times less code.</p> <p>And if you are taking say 2 to 10 arguments, the reduction in code is exponential -- 2x times less with 1 argument, 4x with 2, 8x with 3, 16x with 4, 1024x with 10 arguments.</p> <p>Now, we can get around this via perfect forwarding and SFINAE, allowing you to write a single constructor or function template that takes 10 arguments, does SFINAE to ensure that the arguments are of appropriate types, and then moves-or-copies them into the local state as required. While this prevents the thousand fold increase in program size problem, there can still be a whole pile of functions generated from this template. (template function instantiations generate functions)</p> <p>And lots of generated functions means larger executable code size, which can itself reduce performance.</p> <p>For the cost of a few <code>move</code>s, we get shorter code and nearly the same performance, and often easier to understand code.</p> <p>Now, this only works because we know, when the function (in this case, a constructor) is called, that we will be wanting a local copy of that argument. The idea is that if we know that we are going to be making a copy, we should let the caller know that we are making a copy by putting it in our argument list. They can then optimize around the fact that they are going to give us a copy (by moving into our argument, for example).</p> <p>Another advantage of the 'take by value" technique is that often move constructors are noexcept. That means the functions that take by-value and move out of their argument can often be noexcept, moving any <code>throw</code>s out of their body and into the calling scope (who can avoid it via direct construction sometimes, or construct the items and <code>move</code> into the argument, to control where throwing happens). Making methods nothrow is often worth it.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload