When a function is called that has a return value, temporary memory (usually on the stack) needs to set aside hold the return value because the variable holding the return value in called function goes out of scope and will be destroyed (destructor called) if not a plain old data (POD) type. Prior to C++11, the return value was copied from the temporary into a receiving variable using a copy constructor if not a POD type. If the variable was a large container like a list or vector, this was not efficient. This is why RPN lists were returned by a pointer.
However, with a C++11 move constructor, the return value is moved directly into the variable being constructed or assigned, each having a slightly different mechanism. For a container class like a list or vector, the only thing that is moved is the pointer to the data, not the actual data itself. Consider this example of a function that returns a list and code that calls the function:
std::list<SomeType> someFunction()The new C++11 initializer syntax is used to show that the move constructor is called (but the old assignment syntax does the same thing). Without a move constructor, the local list variable destructor would be called after the return value was copied to a temporary. With the move constructor, the return value is moved directly into the receiving variable and the local variable is reset, and when the destructor for the local is called, it has nothing to do. With a container class, minimal values are copied (essentially a pointer with a little other possible housekeeping like a size). A move constructor has this basic layout:
{
std::list<SomeType> localList;
... list created here ...
return localList;
}
...
std::list<SomeType> list {someFunction()}; ← caller
ClassName(ClassName &&other) :First the members of the receiving variable are initialized with another instance (the return variable). In the body of the move constructor, the other members are initialized to defaults. If one of the members was a pointer to allocated data, the pointer is transferred, and the pointer in the return variable that is about to go out of scope is set to null, so when its destructor is called, it has nothing to delete.
member1{other.member1},
member2{other.member2},
...
{
other.member1{};
other.member2{};
...
}
A move assignment is similar, but is used when the receiving variable (that already exists) is being assigned to a return value, where the caller looks like this:
std::list<SomeType> list;In this case, the value in the receiving variable must be destroyed first and then the return value moved in. Without a move assignment, this is handled by the copy constructor, which destroys the old value, and copies the new value. The destructor for the local variable is called. A move assignment has this basic layout:
...
list = someFunction();
ClassName &operator=(ClassName &&other)For each member, the values of the members are swapped. The standard swap function does this efficiently with move operations where a member is moved into a temporary variable, the other member is moved into the current member, and the temporary moved into the other member. No destructors are called for any of the moves. Here, the other instance is the local variable of the function that is going out of scope. What this does is moves the value of the receiving variable into the local variable, which then gets destroyed. As with any assignment, the reference of the object is returned.
{
std::swap(member1, other.member1);
std::swap(member2, other.member2);
...
return *this;
}
The above descriptions may not be exactly what occurs internally, but it is my interpretation and explains the basic concept behind the move mechanisms. One final mention is that if the object does not have a move constructor or assignment, then the old copy through a temporary mechanism (copy constructor) is used. There are reasons why an object may not have move operations, which will be covered in the next post.
One last note. The implicit sharing feature of the container classes of Qt is another mechanism to make returning instances more efficient. This feature acts like a shared smart pointer. The return value is copied to the temporary, and the use count is incremented. When the local variable is destroyed, the use count is decremented and nothing needs to be destroyed. Shared smart pointers is another mechanism (as already used for RPN item pointers).
No comments:
Post a Comment
All comments and feedback welcomed, whether positive or negative.
(Anonymous comments are allowed, but comments with URL links or unrelated comments will be removed.)