{"id":109059,"date":"2023-11-24T07:00:00","date_gmt":"2023-11-24T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109059"},"modified":"2023-12-06T12:38:02","modified_gmt":"2023-12-06T20:38:02","slug":"20231124-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20231124-00\/?p=109059","title":{"rendered":"On harmful overuse of <CODE>std::move<\/CODE>"},"content":{"rendered":"<p>The C++ <code>std::move<\/code> function casts its parameter to an rvalue reference, which enables its contents to be consumed by another operation. But in your excitement about this new expressive capability, take care not to overuse it.<\/p>\n<pre>std::string get_name(int id)\r\n{\r\n    std::string name = std::to_string(id);\r\n    \/* assume other calculations happen here *\/\r\n    <i>return std::move(name);<\/i>\r\n}\r\n<\/pre>\n<p>You think you are giving the compiler some help by saying &#8220;Hey, like, I&#8217;m not using my local variable <code>name<\/code> after this point, so you can just move the string into the return value.&#8221;<\/p>\n<p>Unfortunately, your help is actually hurting. Adding a <code>std::move<\/code> causes the <code>return<\/code> statement to fail to satisfy <a href=\"http:\/\/eel.is\/c++draft\/class.copy.elision\"> the conditions for copy elision<\/a> (commonly known as Named Return Value Optimization, or NRVO): The thing being returned must be the name of a local variable with the same type as the function return value.<\/p>\n<p>The added <code>std::move<\/code> prevents NRVO, and the return value is move-constructed from the <code>name<\/code> variable.<\/p>\n<pre>std::string get_name(int id)\r\n{\r\n    std::string name = std::to_string(id);\r\n    \/* assume other calculations happen here *\/\r\n    <span style=\"border: solid 1px currentcolor;\">return name;<\/span>\r\n}\r\n<\/pre>\n<p>This time, we return <code>name<\/code> directly, and the compiler can now elide the copy and put the <code>name<\/code> variable directly in the return value slot with no copy. (Compilers are permitted but not required to perform this optimization, but in practice, all compilers will do it if all code paths return the same local variable.)<\/p>\n<p>The other half of the overzealous <code>std::move<\/code> is on the receiving end.<\/p>\n<pre>extern void report_name(std::string name);\r\n\r\nvoid sample1()\r\n{\r\n    <i>std::string name = std::move(get_name());<\/i>\r\n}\r\n\r\nvoid sample2()\r\n{\r\n    <i>report_name(std::move(get_name()));<\/i>\r\n}\r\n<\/pre>\n<p>In these two sample functions, we take the return value from <code>get_name<\/code> and explicitly <code>std::move<\/code> it into a new local variable or into a function parameter. This is another case of trying to be helpful and ending up hurting.<\/p>\n<p>Constructing a value (either a local variable or a function parameter) from a matching value of the same type will be elided: The matching value is stored directly into the local variable or parameter without a copy. But adding a <code>std::move<\/code> prevents this optimization from occurring, and the value will instead be move-constructed.<\/p>\n<pre>extern void report_name(std::string name);\r\n\r\nvoid sample1()\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">std::string name = get_name();<\/span>\r\n}\r\n\r\nvoid sample2()\r\n{\r\n    <span style=\"border: solid 1px currentcolor;\">report_name(get_name());<\/span>\r\n}\r\n<\/pre>\n<p>What&#8217;s particularly exciting is when you combine both mistakes. In that case, you took what would have been a sequence that had no copy or move operations at all and converted it into a sequence that creates two extra temporaries, two extra move operations, and two extra destructions.<\/p>\n<pre>#include &lt;memory&gt;\r\n\r\nstruct S\r\n{\r\n    S();\r\n    S(S const&amp;);\r\n    S(S &amp;&amp;);\r\n    ~S();\r\n};\r\n\r\nextern void consume(S s);\r\n\r\n\/\/ Bad version\r\nS __declspec(noinline) f1()\r\n{\r\n    S s;\r\n    return std::move(s);\r\n}\r\n\r\nvoid g1()\r\n{\r\n    consume(std::move(f1()));\r\n}\r\n<\/pre>\n<p>Here&#8217;s the compiler output for msvc:<\/p>\n<pre>; on entry, rcx says where to put the return value\r\nf1:\r\n    mov     qword ptr [rsp+8], rcx\r\n    push    rbx\r\n    sub     rsp, 48\r\n    mov     rbx, rcx\r\n\r\n    ; construct local variable s on stack\r\n    lea     rcx, qword ptr [rsp+64]\r\n    call    S::S()\r\n\r\n    ; copy local variable to return value\r\n    lea     rdx, qword ptr [rsp+64]\r\n    mov     rcx, rbx\r\n    call    S::S(S &amp;&amp;)\r\n\r\n    ; destruct the local variable s\r\n    lea     rcx, qword ptr [rsp+64]\r\n    call    S::~S()\r\n\r\n    ; return the result\r\n    mov     rax, rbx\r\n    add     rsp, 48\r\n    pop     rbx\r\n    ret\r\n\r\ng1:\r\n    sub     rsp, 40\r\n\r\n    ; call f1 and store into temporary variable\r\n    lea     rcx, qword ptr [rsp+56]\r\n    call    f1()\r\n\r\n    ; copy temporary to outbound parameter\r\n    mov     rdx, rax\r\n    lea     rcx, qword ptr [rsp+48]\r\n    call    S::S(S &amp;&amp;)\r\n\r\n    ; call consume with the outbound parameter\r\n    mov     rcx, rax\r\n    call    consume(S)\r\n\r\n    ; clean up the temporary\r\n    lea     rcx, qword ptr [rsp+56]\r\n    call    S::~S()\r\n\r\n    ; return\r\n    add     rsp, 40\r\n    ret\r\n<\/pre>\n<p>Notice that calling <code>g1<\/code> resulted in the creation of a total of two extra copies of <code>S<\/code>, one in <code>f1<\/code> and another to hold the return value of <code>f1<\/code>.<\/p>\n<p>By comparison, if we use copy elision:<\/p>\n<pre>\/\/ Good version\r\nS __declspec(noinline) f2()\r\n{\r\n    S s;\r\n    return s;\r\n}\r\n\r\nvoid g2()\r\n{\r\n    consume(f2());\r\n}\r\n<\/pre>\n<p>then the msvc code generation is<\/p>\n<pre>; on entry, rcx says where to put the return value\r\nf2:\r\n    push    rbx\r\n    sub     rsp, 48\r\n    mov     rbx, rcx\r\n\r\n    ; construct directly into return value (still in rcx)\r\n    call    S::S()\r\n\r\n    ; and return it\r\n    mov     rax, rbx\r\n    add     rsp, 48\r\n    pop     rbx\r\n    ret\r\n\r\ng2:\r\n    sub     rsp, 40\r\n\r\n    ; put return value of f1 directly into outbound parameter\r\n    lea     rcx, qword ptr [rsp+48]\r\n    call    f2()\r\n\r\n    ; call consume with the outbound parameter\r\n    mov     rcx, eax\r\n    call    consume(S)\r\n\r\n    ; return\r\n    add     rsp, 40\r\n    ret\r\n<\/pre>\n<p>You get similar results with gcc, clang, and <span style=\"text-decoration: line-through;\">icc<\/span> icx.<\/p>\n<p>In gcc, clang, and icx, you can enable the <code>pessimizing-move<\/code> warning to tell you when you make these mistakes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Initial excitement leads to overuse.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109059","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Initial excitement leads to overuse.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109059","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109059"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109059\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109059"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109059"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109059"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}