This article is inspired from Item 23 of Effective Modern C++ by Scott Meyers.
The primary purpose of std::move is to convert an Lvalue to Rvalue, which can potentially make it eligible to be moved. When the object cannot be moved, then copy semantics is used.
Take the following example -
We see that funcA takes the parameter by reference. Since the parameter is not const, the underlying resource, i.e the character array is transferred to the result string. This leaves str1 as null ( nullptr ).
On the other hand, funcB takes the parameter by const reference. Even though std::move does change parameter into Rvalue, it's contents cannot be transferred because by the very definition, const variables cannot change. Hence instead of moving the parameter to result, the string is copied.
std::forward does the casting of it's parameters similar std::move, albeit differently. std::forward casts the argument to Rvalue, only if the argument is bound to an Rvalue. To understand this further, let's look at this example -
Lets first examine the "Test Run 1" i.e the calls to displayWithoutForward.
The first call is invoked, with aObj, a Lvalue. So when, displayWithoutForward calls funcA, it calls the Lvalue version.
The second call is invoked with return value of function, returnA, a Rvalue. But when, displayWithoutForward calls funcA, the parameter loses it "Rvalueness" and Lvalue version of funcA is called.
Now examining the "Test Run 2" i.e the calls to displayWithForward.
The first done is invokedwith bObj, a Lvalue. So when, displayWithForward calls funcA, it calls the Lvalue version.The call to std::forward<T> does not have any effect.
The second call is invoked with return value of function, returnA, a Rvalue. When displayWithForward calls funcA with std::forward<T>, the parameter retains it 's "Rvalueness" and the Rvalue version of funcA is called. This feature is also called perfect forwarding.
Hence std::forward only casts the argument to Rvalue conditionally, whereas std::move casts the argument to Rvalue unconditionally.
To use std::forward<T> , include<utility>.
The primary purpose of std::move is to convert an Lvalue to Rvalue, which can potentially make it eligible to be moved. When the object cannot be moved, then copy semantics is used.
Take the following example -
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
#include <iostream> | |
#include <string> | |
void funcA(std::string& param) | |
{ | |
std::string result(std::move(param)); | |
std::cout << "Inside funcA - result : " << result << '\n'; | |
} | |
void funcB(const std::string& param) | |
{ | |
std::string result(std::move(param)); | |
std::cout << "Inside funcB - result : " << result << '\n'; | |
} | |
int main() | |
{ | |
std::string str1 = "Effective Modern C++"; | |
std::string str2 = "Scott Meyers"; | |
std::cout << "Initial state of variables - " << str1 << " - " << str2 << '\n'; | |
funcA(str1); | |
std::cout << "After passing str1 - " << str1 << '\n'; | |
funcB(str2); | |
std::cout << "After passing str2 - " << str2 << '\n'; | |
} | |
/* Output | |
Initial state of variables - Effective Modern C++ - Scott Meyers | |
Inside funcA - result : Effective Modern C++ | |
After passing str1 - | |
Inside funcB - result : Scott Meyers | |
After passing str2 - Scott Meyers | |
*/ |
We see that funcA takes the parameter by reference. Since the parameter is not const, the underlying resource, i.e the character array is transferred to the result string. This leaves str1 as null ( nullptr ).
On the other hand, funcB takes the parameter by const reference. Even though std::move does change parameter into Rvalue, it's contents cannot be transferred because by the very definition, const variables cannot change. Hence instead of moving the parameter to result, the string is copied.
std::forward does the casting of it's parameters similar std::move, albeit differently. std::forward casts the argument to Rvalue, only if the argument is bound to an Rvalue. To understand this further, let's look at this example -
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
#include <iostream> | |
#include <string> | |
#include <memory> | |
#include <random> | |
#include <utility> | |
class A | |
{ | |
std::unique_ptr<int> x; | |
public: | |
A() | |
{ | |
x = std::make_unique<int>(rand() % 50); | |
} | |
A(A& other) : x(std::make_unique<int>(*other.x)) | |
{ | |
std::cout << "Copied from source " << *other.x << " to " << *x << '\n'; | |
} | |
A(A&& other) : x(std::move(other.x)) | |
{ | |
std::cout << "Move Op called. Content is " << *x << '\n'; | |
} | |
void display() | |
{ | |
std::cout << "My content is " << *x << '\n'; | |
} | |
}; | |
void funcA(A&& param) | |
{ | |
std::cout << "funcA rvalue param called - "; | |
param.display(); | |
} | |
void funcA(A& param) | |
{ | |
std::cout << "funcA lvalue param called - "; | |
param.display(); | |
} | |
A returnA() | |
{ | |
return A{}; | |
} | |
template<typename T> | |
void displayWithoutForward(T&& param) | |
{ | |
funcA(param); | |
} | |
template<typename T> | |
void displayWithForward(T&& param) | |
{ | |
funcA(std::forward<T>(param)); | |
} | |
int main() | |
{ | |
srand(time_t(0)); | |
std::cout << "Test Run 1\n"; | |
A aObj; | |
displayWithoutForward(aObj); | |
displayWithoutForward(returnA()); | |
std::cout << "\nTest Run 2\n"; | |
A bObj; | |
displayWithForward(bObj); | |
displayWithForward(returnA()); | |
} | |
/* Output | |
Test Run 1 | |
funcA lvalue param called - My content is 33 | |
funcA lvalue param called - My content is 36 | |
Test Run 2 | |
funcA lvalue param called - My content is 27 | |
funcA rvalue param called - My content is 15 | |
*/ |
Lets first examine the "Test Run 1" i.e the calls to displayWithoutForward.
The first call is invoked, with aObj, a Lvalue. So when, displayWithoutForward calls funcA, it calls the Lvalue version.
The second call is invoked with return value of function, returnA, a Rvalue. But when, displayWithoutForward calls funcA, the parameter loses it "Rvalueness" and Lvalue version of funcA is called.
Now examining the "Test Run 2" i.e the calls to displayWithForward.
The first done is invokedwith bObj, a Lvalue. So when, displayWithForward calls funcA, it calls the Lvalue version.The call to std::forward<T> does not have any effect.
The second call is invoked with return value of function, returnA, a Rvalue. When displayWithForward calls funcA with std::forward<T>, the parameter retains it 's "Rvalueness" and the Rvalue version of funcA is called. This feature is also called perfect forwarding.
Hence std::forward only casts the argument to Rvalue conditionally, whereas std::move casts the argument to Rvalue unconditionally.
To use std::forward<T> , include<utility>.
Modern (Effective) C++ - Understanding std::move and std::forward
Reviewed by zeroingTheDot
on
February 11, 2018
Rating:

No comments: