Move Semantics
In this post we are going to take a look at move semantics. Move semantics rely on the concept of rvalues, which were introduced in C++11. First we must make the distinction between lvalues and rvalues.
lvalues are called as such because they have traditionally appeared on the left hand side of an assignment operator and designate an object (among other things).
rvalues are called as such because they have traditionally appeared on the right hand side of an operator and typically designate temporary objects (possibly other things).
How do we refer to such things in C++? Like so...
We can make use of std::move which will essentially allow us to treat created objects as temporary ones. More precisely, std::move is a cast that produces an rvalue-reference to and object to enable moving from it. Although we have to keep in mind that we can no longer say that the passed object is non-empty. For example,
lvalues are called as such because they have traditionally appeared on the left hand side of an assignment operator and designate an object (among other things).
rvalues are called as such because they have traditionally appeared on the right hand side of an operator and typically designate temporary objects (possibly other things).
How do we refer to such things in C++? Like so...
X& // lvalue reference X&& // rvalue referenceWhat are the uses of such things? One of the main ramifications of rvalue references is move semantics. What this allows us to do is create objects from temporary objects without having to make a deep copy.
#include<iostream> #include<list> class BigVec { private: int m_size; double* m_data; public: // copy constructor BigVec(const BigVec& bv) : m_size(bv.m_size) { m_data = new double[bv.m_size]; for(int i=0; i<m_size; i++) { m_data[i] = bv.m_data[i]; } } // copy assignment operator BigVec& operator=(BigVec& bv) { m_data = new double[bv.m_size]; for(int i=0; i<m_size; i++) { m_data[i] = bv.m_data[i]; } return *this; } // move constructor BigVec(BigVec&& bv) : m_size(bv.m_size), m_data(bv.m_data) { bv.m_data = nullptr; } // move assignment operator BigVec& operator=(BigVec&& bv) { if(this == &bv) return *this; // is self referential ? delete m_data; // delete current data m_data = bv.m_data; m_size = bv.m_size; // reset object as resources have been moved bv.m_data = nullptr; bv.m_size = 0; return *this; } // toy constructor BigVec(int size) : m_size(size) { m_data = new double[size]; for(int i = 0; i < size; i++) m_data[i] = 0; } int getSize() { return m_size; } ~BigVec() { delete m_data; } }; BigVec CreateBigVec() { return BigVec(100); } int main() { // Copy constructor is called BigVec v = CreateBigVec(); BigVec vec(v); // Move constructor is called std::list<BigVec> listOfBigVecs; listOfBigVecs.push_back(CreateBigVec()); // Move assignment operator is called vec = BigVec(10); // Calls the toy constructor BigVec vec1 = BigVec(1); // this is the same as BigVec vec2(1); std::cout << vec1.getSize() << std::endl; return 0; }The example above shows how the different constructors are called. Perhaps the most significant is the move constructor. We can execute the following code, which will fill our list with BigVec's. Each time we add an item to the list the move constructor is being called.
for(int i = 100; i < 1000; i++) { listOfBigVecs.push_back(BigVec(i)); }
The move constructor requires a fixed number of operations per call, and so the above code is linear in time complexity.
for(int i = 100; i < 1000; i++) { BigVec temp(i); listOfBigVecs.push_back(temp); }The above code would require 899 calls to the copy constructor, which takes linear time. This immediately makes our code quadratic in time complexity.
We can make use of std::move which will essentially allow us to treat created objects as temporary ones. More precisely, std::move is a cast that produces an rvalue-reference to and object to enable moving from it. Although we have to keep in mind that we can no longer say that the passed object is non-empty. For example,
auto v = CreateBigVec(); BigVec vec1(v); // copy BigVec vec2(std::move(v)); // move
Comments
Post a Comment