{"id":31231,"date":"2022-10-24T16:00:31","date_gmt":"2022-10-24T16:00:31","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/cppblog\/?p=31231"},"modified":"2022-10-24T16:03:13","modified_gmt":"2022-10-24T16:03:13","slug":"improving-copy-and-move-elision","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cppblog\/improving-copy-and-move-elision\/","title":{"rendered":"Improving Copy and Move Elision"},"content":{"rendered":"<p>With Visual Studio 2022 version 17.4 Preview 3, we&#8217;ve significantly increased the number of situations where we do copy or move elision and given users more control over whether these transformations are enabled.<\/p>\n<h2>What are copy and move elision?<\/h2>\n<p>When a <code>return<\/code> keyword in a C++ function is followed by an expression of non-primitive type, the execution of that return statement copies the result of the expression into the return slot of the calling function. To do this, the copy or move constructor of the non-primitive type is called. Then, as part of exiting the function, destructors for function-local variables are called, likely including any variables named in the expression following the <code>return<\/code> keyword.<\/p>\n<p>The C++ specification allows the compiler to construct the returned object directly in the return slot of the calling function, eliding the copy or move constructor executed as part of the return. Unlike most other optimizations, this transformation is allowed to have an observable effect on the program&#8217;s output &#8211; namely, the copy or move constructor and associated destructor are called one less time.<\/p>\n<h2>Mandatory copy\/move elision in Visual Studio<\/h2>\n<p>The C++ standard <em>requires<\/em> copy or move elision when the returned value is initialized as part of the <code>return<\/code> statement (such as when a function with return type <code>Foo<\/code> returns <code>return Foo()<\/code>). The Microsoft Visual C++ compiler always performs copy and move elision for return statements where it is required to do so, regardless of the flags passed to the compiler. This behavior is unchanged.<\/p>\n<h2>Changes to optional copy\/move elision in Visual Studio 17.4 Preview 3<\/h2>\n<p>When the returned value is a named variable, the compiler <em>may<\/em> elide the copy or move but is not required to do so. The standard still requires a copy or move constructor to be defined for the named return variable, even if the compiler elides the constructor in all cases. Prior to Visual Studio 2022 version 17.4 Preview 3, when optimizations were disabled (such as with the <code>\/Od<\/code> compiler flag or for functions marked with <code>#pragma optimize(\"\", off)<\/code>) the compiler would only perform mandatory copy and move elision. With the <code>\/O2<\/code> flag, the compiler would perform optional copy or move elision for optimized functions with simple control flow.<\/p>\n<p>Starting with Visual Studio 2022 version 17.4 Preview 3, we are giving developers the option for consistency with the new <code>\/Zc:nrvo<\/code> compiler flag. The <code>\/Zc:nrvo<\/code> flag will be passed by default when code is compiled with the <code>\/O2<\/code> flag, the <code>\/permissive-<\/code> flag, or when compiling for <code>\/std:c++20<\/code> or later. When this flag is passed, copy and move elision will be performed wherever possible. We would like to turn <code>\/Zc:nrvo<\/code> on by default in a future release.<\/p>\n<p>Starting with Visual Studio 2022 version 17.4 Preview 3, optional copy\/move elision can also be explicitly disabled with the <code>\/Zc:nrvo-<\/code> flag. It is impossible to disable mandatory copy\/move elision.<\/p>\n<p>In Visual Studio 2022 version 17.4 Preview 3, we are also increasing the number of places where we do copy\/move elision when optional copy\/move elision is enabled with the <code>\/Zc:nrvo<\/code>, <code>\/O2<\/code>, <code>\/permissive-<\/code>, or <code>\/std:c++20<\/code> or later flags.<\/p>\n<table>\n<thead>\n<tr>\n<th><\/th>\n<th>Earlier versions of Visual Studio<\/th>\n<th>Visual Studio 17.4 Preview 3 and later<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Mandatory copy\/move elision<\/td>\n<td>Always occurs.<\/td>\n<td>Always occurs.<\/td>\n<\/tr>\n<tr>\n<td>Optional copy\/move elision for return of named variable in function without loops or exception handling<\/td>\n<td>Occurs under <code>\/O2<\/code> unless the function has multiple returned symbols with overlapping lifetimes or the type&#8217;s copy or move constructor has default arguments.<\/td>\n<td>Does not occur under <code>\/Zc:nrvo-<\/code>. Otherwise, occurs under <code>\/O2<\/code>, <code>\/permissive-<\/code>, <code>\/std:c++20<\/code> or later, or <code>\/Zc:nrvo<\/code> unless the function has multiple returned symbols with overlapping lifetimes.<\/td>\n<\/tr>\n<tr>\n<td>Optional copy\/move elision for return of named variable in a loop<\/td>\n<td>Never occurs.<\/td>\n<td>Does not occur under <code>\/Zc:nrvo-<\/code>. Otherwise, occurs under <code>\/O2<\/code>, <code>\/permissive-<\/code>, <code>\/std:c++20<\/code> or later, or <code>\/Zc:nrvo<\/code> unless the function has multiple returned symbols with overlapping lifetimes.<\/td>\n<\/tr>\n<tr>\n<td>Optional copy\/move elision for return of named variable in functions with exception handling<\/td>\n<td>Never occurs.<\/td>\n<td>Does not occur under <code>\/Zc:nrvo-<\/code>. Otherwise, occurs under <code>\/O2<\/code>, <code>\/permissive-<\/code>, <code>\/std:c++20<\/code> or later, or <code>\/Zc:nrvo<\/code> unless the function has multiple returned symbols with overlapping lifetimes.<\/td>\n<\/tr>\n<tr>\n<td>Optional copy\/move elision for return of named variable when the copy or move constructor has additional default arguments<\/td>\n<td>Never occurs.<\/td>\n<td>Does not occur under <code>\/Zc:nrvo-<\/code>. Otherwise, occurs under <code>\/O2<\/code>, <code>\/permissive-<\/code>, <code>\/std:c++20<\/code> or later, or <code>\/Zc:nrvo<\/code> unless the function has multiple returned symbols with overlapping lifetimes.<\/td>\n<\/tr>\n<tr>\n<td>Optional copy\/move elision for throw of a named variable<\/td>\n<td>Never occurs.<\/td>\n<td>Never occurs.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Examples of optional copy\/move elision<\/h2>\n<p>The simplest example of optional copy or move elision is a function such as:<\/p>\n<pre><code class=\"language-C++\">Foo SimpleReturn() {\r\n    Foo result;\r\n    return result;\r\n}<\/code><\/pre>\n<p>Earlier versions of the MSVC compiler already elided the copy or move of <code>result<\/code> into the return slot in this case if the <code>\/O2<\/code> flag was passed. In Visual Studio 2022 version 17.4 Preview 3, the copy or move is also elided if the <code>\/permissive-<\/code>, <code>\/std:c++20<\/code> or later, or <code>\/Zc:nrvo<\/code> flags are passed, and retained if the <code>\/Zc:nrvo-<\/code> flag is passed.<\/p>\n<p>Starting with Visual Studio 2022 version 17.4 Preview 3, we now perform copy\/move elision in the following additional cases if the <code>\/O2<\/code>, <code>\/permissive-<\/code>, <code>\/std:c++20<\/code> or later, or <code>\/Zc:nrvo<\/code> flags are passed to the compiler and the <code>\/Zc:nrvo-<\/code> flag is not:<\/p>\n<h3>Return inside a loop<\/h3>\n<pre><code class=\"language-C++\">Foo ReturnInALoop(int iterations) {\r\n    for (int i = 0; i &lt; iterations; ++i) {\r\n        Foo result;\r\n        if (i == (iterations \/ 2)) {\r\n            return result;\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<p>The <code>result<\/code> object will be properly constructed at the start of each iteration of the loop and destructed at the end of each iteration. On the iteration where <code>result<\/code> is returned, its destructor will not be called on exit from the function. The function&#8217;s caller will destroy the returned object when it falls out of scope in that function.<\/p>\n<h3>Return with exception-handling<\/h3>\n<pre><code class=\"language-C++\">Foo ReturnInTryCatch() {\r\n    try {\r\n        Foo result;\r\n        return result;\r\n    } catch (...) {}\r\n}<\/code><\/pre>\n<p>The copy or move of the <code>result<\/code> object will now be elided if the <code>\/O2<\/code>, <code>\/permissive-<\/code>, <code>\/std:c++20<\/code> or later, or <code>\/Zc:nrvo<\/code> flags are passed and the <code>\/Zc:nrvo-<\/code> flag is not. We also now properly handle more complex cases such as:<\/p>\n<pre><code class=\"language-C++\">int n;\r\n\r\nvoid throwFirstThreeIterations() {\r\n    ++n;\r\n    if (n &lt;= 3) throw n;\r\n}\r\n\r\nFoo ComplexTryCatch()\r\n{\r\nLabel1:\r\n    Foo result;\r\n\r\n    try {\r\n        throwFirstThreeIterations();\r\n        return result;\r\n    }\r\n    catch(...) {\r\n        goto Label1;    \r\n    }\r\n}<\/code><\/pre>\n<p>The <code>result<\/code> object will be constructed in the return slot for the caller function and no copy\/move constructor or destructor will be called for it on a successful return. When an exception is thrown, whether or not the <code>result<\/code> object is destructed is determined by which exception-handling flags are passed to the compiler. By default, no stack-unwinding will occur and therefore no destructors will be called. However, if stack-unwinding exception handling is enabled with the <code>\/EHs<\/code>, <code>\/EHa<\/code>, or <code>\/EHr<\/code> flags, <code>goto Label1<\/code> will cause <code>result<\/code>&#8216;s destructor to be called because it jumps to before <code>result<\/code> is initialized. Either way, when the expression <code>Foo result<\/code> is reached again, the object will be constructed again in the return slot.<\/p>\n<h3>Copy constructors with default arguments<\/h3>\n<p>We now properly detect that a copy or move constructor with default arguments is still a copy or move constructor, and therefore can be elided in the cases above. A copy constructor with default parameters will look something like the following:<\/p>\n<pre><code class=\"language-C++\">struct StructWithCopyConstructorDefaultParam {\r\n   int X;\r\n\r\n   StructWithCopyConstructorDefaultParam(int x) : X(x) {}\r\n   StructWithCopyConstructorDefaultParam(StructWithCopyConstructorDefaultParam const&amp; original, int defaultParam = 0) :\r\n      X(original.X + defaultParam) {\r\n      printf(\"Copy constructor called.\\n\");\r\n   }\r\n};\r\n<\/code><\/pre>\n<h2>Limitations on NRVO<\/h2>\n<p>Although the MSVC compiler now performs copy and move elision in many more situations, it is not always possible to perform copy\/move elision. To see why this is true, consider the following function:<\/p>\n<pre><code class=\"language-C++\">Foo WhichShouldIReturn(bool condition) {\r\n    Foo resultA;\r\n    if (condition) {\r\n        Foo resultB;\r\n        return resultB;\r\n    }\r\n    return resultA;\r\n}<\/code><\/pre>\n<p>Copy elision constructs the object to be returned in the return slot, but which object should be constructed in the return slot in this case? For the copy of <code>resultA<\/code> to be elided at <code>return resultA<\/code>, it must be constructed in the return slot. However, if <code>condition<\/code> is true, <code>resultB<\/code> will need to be constructed in the return slot before <code>resultA<\/code> is destroyed. There is no way to perform copy elision for both paths.<\/p>\n<p>We currently choose to avoid doing optional copy\/move elision on all paths in a function if copy\/move elision is impossible on any path. However, changes to inlining decisions, dead code elimination, and other optimizations can change whether copy or move elision is possible. For this reason, it is never safe to write code that depends on certain behavior for copy\/move elision of named variables unless all optional copy\/move elision is disabled with <code>\/Zc:nrvo-<\/code>.<\/p>\n<p>As long as stack-unwinding exception handling is enabled or no exceptions are thrown, it is still safe to assume that every constructer call has a matching destructor call.<\/p>\n<h2>Feedback<\/h2>\n<p>We encourage you to try out this update in the latest Visual Studio 2022 version 17.4 Preview. Please let us know what you think or any issues you encounter. We can be reached via the comments below, via twitter (<a href=\"https:\/\/twitter.com\/visualc\">@visualC<\/a>) or via <a href=\"https:\/\/developercommunity.visualstudio.com\/\">Developer Community<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With Visual Studio 2022 version 17.4 Preview 3, we&#8217;ve significantly increased the number of situations where we do copy or move elision and given users more control over whether these transformations are enabled. What are copy and move elision? When a return keyword in a C++ function is followed by an expression of non-primitive type, [&hellip;]<\/p>\n","protected":false},"author":18811,"featured_media":35994,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-31231","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cplusplus"],"acf":[],"blog_post_summary":"<p>With Visual Studio 2022 version 17.4 Preview 3, we&#8217;ve significantly increased the number of situations where we do copy or move elision and given users more control over whether these transformations are enabled. What are copy and move elision? When a return keyword in a C++ function is followed by an expression of non-primitive type, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/31231","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/users\/18811"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/comments?post=31231"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/31231\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media\/35994"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media?parent=31231"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/categories?post=31231"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/tags?post=31231"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}