I have been learning lately about C++11 move semantics, originally being a C programmer who is concerned with the performance it was very interesting to explore such an important optimization feature. I had to go through many articles explaining rvalues, lvalues and the && operator. I believe you still need to learn these topics to develop a firm understanding of the topic but in my point of view the move semantics can be explained intuitively by drawing the similarity between it and python’s object execution model.
The goal of move semantics is to avoid copying large objects unnecessarily when they are passed from one function to another.
How are objects passed in python?
Interestingly enough, python was created with that particular optimization in mind.
Firstly, It’s important to understand the subtle difference between the interpretation of the following statement in python and C++
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
str = 'medium' |
In C++ the variable str is created in memory, this location in memory contains the data medium and str refers to this memory location.
However in python the story is different. Although this statement also creates an object with the same content, but str isn’t the object reference. str is a merely a name and the statement binds it to the object. This subtle deviation may not look crucial but it makes all the difference when passing arguments to a function.
Consider the following 2 code snippets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def modify(arg): | |
arg = 'hard' | |
str = 'medium' | |
modify(str) | |
print(str) # >> medium |
In the first snippet, an immutable object is created containing the data ‘medium’ and str is bound to this object. When the method modify is called a new name arg is also bound to ‘medium’, but as soon as line 2 is executed the binding of arg is relocated to another new object with the data ‘hard’ because there is no way to change an immutable. The resulting behavior appears as a conventional pass by value call.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def modify(arg): | |
arg += [1] | |
l = [2] | |
modify(l) | |
print(l) # >> [2, 1] |
On the other hand, the behavior depicted in the second snippet resembles a pass by reference call as modify here doesn’t rebind arg to a new object because this object is mutable. arg and l are two names bound to the same object. When line 2 is executed this object is modified.
In python object references are passed by value, in other words objects are moved.
C++ move semantics in layman terms
Before the move semantics there were a couple of ways to avoid unnecessary large copies by passing pointers around which is somehow considered an unsafe practice compared to use an instantiated object which auto destructs.
Move semantics are based on rvalue references. An rvalue appears on the right hand side of the assignment statement. Example of an rvalue could be “medium” here:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
string str = "medium"; |
Also the function getName() can return an rvalue because it appears on the right side of the statement.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
string getName() { | |
string str = "medium"; | |
return s; | |
} | |
// getName() returns an rvalue | |
string&& name = getName(); |
name is an rvalue reference.
I like to think of an rvalue as a python object and the rvalue reference as the name bound to this object. The difference being that an rvalue can’t exist on its own while python relies on garbage collection to clean up objects which are no longer bound to any scope.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Obj { | |
//default constructor | |
Obj(int size){ | |
.. | |
} | |
//copy constructor | |
Obj(const Obj& other){ | |
.. | |
} | |
//assignment operator | |
Obj& operator= (const Obj& other){ | |
.. | |
} | |
} | |
Obj B; | |
B = Obj(1000); |
Without a move constructor the last line assignment will
- allocate memory for the temporary object (and destruct it later)
- allocate memory again at the destination
Adding a move constructor to the object definition will optimize out the second allocation and reuse the previous allocation for the destination.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Obj { | |
… | |
//move constructor | |
Obj(const Obj&& other){ | |
.. | |
} | |
//move assignment operator | |
Obj& operator= (const Obj&& other){ | |
.. | |
} | |
} |
Conclusion
Having this analogy in head, comprehending the power of move semantics was no more complicated. When a move constructor is defined for an object, it will be possible to rebind it to different name while avoiding expensive copies.
Resources
https://medium.com/@meghamohan/mutable-and-immutable-side-of-python-c2145cf72747
https://www.internalpointers.com/post/c-rvalue-references-and-move-semantics-beginners
https://mbevin.wordpress.com/2012/11/20/move-semantics/
https://stackoverflow.com/questions/3106110/what-is-move-semantics