Skip to content

Move constructors (i.e., std::move): When and How to Use Them

amirroth edited this page Jan 13, 2023 · 3 revisions

We do not really use "move constructors" in EnergyPlus much, but maybe we should. One place where this could be handy is to avoid making copies of strings. Strings are a convenient example, but this will work with any compound object like std::vector.

A std::string is a two-part "compound" object. There is the array that contains the actual characters. This part lives in a section of memory called the "heap" and is dynamically allocated, deallocated, and can be resized. Then there is the header/wrapper that contains a pointer to the character array plus some other information, e.g., the amount of memory currently allocated to the array, maybe the length of the current string in the array. This part lives in the "enclosing context". If the std::string is declared as a local variable, this header is part of the stack frame. If the std::string is a member in an object, the header is "inlined" into in the object.

A normal string copy, i.e., std::string s1 = s2;, calls the "copy constructor" of the string template to perform a "deep copy". First, it copies the header s2 header into s1. This is usually fast because the header is small. Then it allocates another character array in heap memory for s1 to point to and copies the contents of s2's character array into it. So now you have two copies of the header and two copies of the character array. Each header points to its own private character array.

It is not possible to avoid copying the header, but that's okay because the header is small. It is possible to avoid making a deep copy of the character array on the heap. You do it like this: std::string s1 = std::move(s2);. This activates the "move constructor" which copies the s2 header into s1, including the pointer to the character array pointed to by s2. Essentially, s2 "hands off" the character array in heap memory to s1. Now you have two copies of the header, but only one copy of the character array in the heap. You've done a "shallow copy" or a "move".

The question is when do you want to do a "shallow copy" as opposed to a "deep copy"? Well, you want to do a "shallow copy" when you are sure that s2 will not be accessed anymore. One way of guaranteeing that is s2 goes out of scope. And one example of s2 going out of scope is when s2 is created inside a function and then returned by that function. For instance, you can do std::string s1 = std::move(getAlphaFieldValue()); safely because the return value of getAlphaFieldValue() is out of scope, i.e., there is no way to access it anymore, after this statement. [Important Update: Apparently, this guidance is quite outdated. With the advent of RVO (Return Value Optimization) in which objects that are returned by functions are constructed into their final destination rather than constructed onto the stack and then copied into their final destination, the new guidance is to not use std::move on return values to allow RVO to proceed. I am not sure why RVO cannot proceed even with std::move but some compilers disable RVO in this situation.]

Clone this wiki locally