Relating C++ move semantics to python objects

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++


str = 'medium'

view raw

assignment.py

hosted with ❤ by GitHub

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


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.


def modify(arg):
arg += [1]
l = [2]
modify(l)
print(l) # >> [2, 1]

view raw

call-by-ref.py

hosted with ❤ by GitHub

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:


string str = "medium";

view raw

assignment.cpp

hosted with ❤ by GitHub

Also the function getName() can return an rvalue because it appears on the right side of the statement.


string getName() {
string str = "medium";
return s;
}
// getName() returns an rvalue
string&& name = getName();

view raw

rvalue.cpp

hosted with ❤ by GitHub

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.


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);

view raw

copy.cpp

hosted with ❤ by GitHub

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.


class Obj {
//move constructor
Obj(const Obj&& other){
..
}
//move assignment operator
Obj& operator= (const Obj&& other){
..
}
}

view raw

move.cpp

hosted with ❤ by GitHub

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

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: