c++ - When to prefer const lvalue reference over rvalue reference templates -


currently reading codebase cpr requests library: https://github.com/whoshuu/cpr/blob/master/include/cpr/api.h

noticed interface library uses perfect forwarding quite often. learning rvalue references relatively new me.

from understanding, benefit rvalue references, templating, , forwarding function call being wrapped around take arguments rvalue reference rather value. avoids unnecessary copying. prevents 1 having generate bunch of overloads due reference deduction.

however, understanding, const lvalue reference same thing. prevents need overloads , passes reference. caveat if function being wrapped around takes non-const reference, won't compile.

however if within call stack won't need non-const reference, why not pass const lvalue reference?

i guess main question here is, when should use 1 on other best performance? attempted test below code. got following relatively consistent results:

compiler: gcc 6.3 os: debian gnu/linux 9

<<<< passing rvalue! const l value: 2912214 rvalue forwarding: 2082953 passing lvalue! const l value: 1219173 rvalue forwarding: 1585913 >>>> 

these results stay consistent between runs. appears rvalue arg, const l value signature slower, though i'm not sure why, unless i'm misunderstanding , const lvalue reference in fact make copy of rvalue.

for lvalue arg, see counter, rvalue forwarding slower. why be? shouldn't reference deduction produce reference lvalue? if thats case shouldn't more or less equivalent const lvalue reference in terms of performance?

#include <iostream> #include <string> #include <utility> #include <time.h>  std::string func1(const std::string& arg) {     std::string test(arg);     return test; }  template <typename t> std::string func2(t&& arg) {     std::string test(std::forward<t>(arg));     return test; }  void wrap1(const std::string& arg) {     func1(arg); }  template <typename t> void wrap2(t&& arg) {     func2(std::forward<t>(arg)); }  int main() {      auto n = 100000000;       /// passing rvalue      std::cout << "passing rvalue!" << std::endl;       // test const l value      auto t = clock();      (int = 0; < n; ++i)          wrap1("test");      std::cout << "const l value: " << clock() - t << std::endl;       // test rvalue forwarding      t = clock();      (int = 0; < n; ++i)          wrap2("test");      std::cout << "rvalue forwarding: " <<  clock() - t << std::endl;       std::cout << "passing lvalue!" << std::endl;       /// passing lvalue      std::string arg = "test";       // test const l value      t = clock();      (int = 0; < n; ++i)          wrap1(arg);      std::cout << "const l value: " << clock() - t << std::endl;       // test rvalue forwarding      t = clock();      (int = 0; < n; ++i)          wrap2(arg);      std::cout << "rvalue forwarding: " << clock() - t << std::endl;  } 

first of all, here are different results code. mentioned in comments, compiler , settings important. in particular, may notice cases have similar runtime, except first one, twice slow.

passing rvalue! const l value: 1357465 rvalue forwarding: 669589 passing lvalue! const l value: 744105 rvalue forwarding: 713189 

let's @ happens in each case.

1) when calling wrap1("test"), since signature of function expects const std::string &, char array passing implicitly converted temporary std::string object on every call (i.e. n times), involves copy* of value. const reference temporary passed func1, std::string constructed it, again involves copy (since it's const reference, cannot moved from, despite being in fact temporary). though function returns value, due rvo copy guaranteed elided if return value used. in case return value not used, , i'm not entirely sure whether standard allows compiler optimize away construction of temp. suspect not, since in general such construction have observable side effects (and results suggest not optimized away). sum up, full-on construction , destruction of std::string performed twice in case.

2) when calling wrap2("test"), argument type const char[5], , gets forwarded rvalue reference way func2, std::string constructor const char[] called copies value. deduced type of template parameter t const char[5] && and, quite obviously, cannot moved despite being rvalue reference (due both being const , not being std::string). compared previous case, construction/destruction of string happens once per call (the const char[5] literal in memory , incurs no overhead).

3) when calling wrap1(arg), passing lvalue const string & through chain, , 1 copy constructor called in func1.

4) when calling wrap2(arg), similar previous case, since deduced type t const std::string &.

5) i'm assuming test designed demonstrate advantage of perfect forwarding when copy of argument needs made @ bottom of call chain (hence creation of temp). in case, need replace "test" argument in first 2 cases std::string("test") in order have std::string && argument, , fix perfect forwarding std::forward<t>(arg), mentioned in comments. in case, the results are:

passing rvalue! const l value: 1314630 rvalue forwarding: 595084 passing lvalue! const l value: 712461 rvalue forwarding: 720338 

which similar had before, invoking move constructor.

i hope helps explain results. there may other issues related inlining of function calls , other compiler optimizations, explain smaller discrepancies between cases 2-4.

as question approach use, suggest reading scott meyer's "effective modern c++" items 23-30. apologies book reference instead of direct answer, there no silver bullet, , optimal choice case-dependent, it's better understand trade-offs of each design decision.


* copy constructor may or may not involve dynamic memory allocation due short string optimization; ytoledano bringing in comments. also, i've implicitly assumed throughout answer copy more expensive move, not case.


Comments

Popular posts from this blog

angular - Ionic slides - dynamically add slides before and after -

minify - Minimizing css files -

Add a dynamic header in angular 2 http provider -