For more exciting content, please pay attention to wechat public number: Back-end technology Cabin
Rvalue semantics in modern C++
Of all the features of modern C++, rvalue semantics (STD ::move and STD ::forward) are probably one of the most magical and difficult. This paper briefly introduces the principle and use of rvalue semantics in modern C++.
1 What is an lvalue, what is an rvalue?
int a = 0; // a is an lvalue and 0 is an rvalue
int b = rand(a);// b is an lvalue, and rand() is an rvalue
Copy the code
The lvalue is to the left of the equals sign, and the rvalue is to the right
An lvalue has a name and can obtain its memory address according to an lvalue, while an rvalue has no name and cannot obtain an address according to an rvalue.
2 Reference the overlay rule
Lvalue references A& and rvalue references A&& can be superimposed on each other as follows:
A& + A& = A&
A& + A&& = A&
A&& + A& = A&
A&& + A&& = A&&
Copy the code
For example, in the template function void foo(T&& x) :
- if
T
isint&
Type,T&&
forint&
.x
Is lvalue semantics - if
T
isint&&
Type,T&&
forint&&
.x
Is rvalue semantics
That is, foo can be passed regardless of whether the input parameter x is lvalue or rvalue. The difference is that in both cases, the compiler derives the type of the template parameter T differently.
3 std::move
3.1 What?
The STD ::move function was introduced in C++11 to implement movement semantics. It is used to move the contents of a temporary variable (and possibly an lvalue) directly to the assigned lvalue object.
3.2 according to?
Now that we know what STD :: Move does, what benefits does it bring to our brick-moving job? For example:
If class X contains a pointer to a resource, the copy constructor of class X is defined in lvalue semantics as follows:
X::X()
{
// Request resource (pointer)
}
X::X(const X& other)
{
// ...
// Destroy resources
// Clone the resource in other
// ...
}
X::~X()
{
// Destroy resources
}
Copy the code
Assume the following application code. TMP is no longer used after it is assigned to a.
X tmp;
/ /... After a series of initializations...
X a = tmp;
Copy the code
In the code above, perform the steps:
- The default constructor is executed once (the default constructor is TMP)
- Execute the copy constructor again (copy builds the A object)
- Execute destructor when exiting scope (destruct TMP and a objects)
From a resource perspective, the above code performs a total of two resource requests and three resource releases.
X a = TMP; Can object A ‘steal’ TMP resources directly for our use without affecting the original function? The answer is yes.
X::X(const X& other)
{
Swap this and other resources using STD ::swap
}
Copy the code
By ‘stealing’ resources from the object TMP, the overhead of resource requisition and release is reduced. The STD ::swap pointer cost is minimal and negligible.
3.3 How?
Now that we know what STD :: Move is going to do, how exactly does it work?
template<class T>
typename remove_reference<T>::type&&
std::move(T&& a) noexcept
{
typedef typename remove_reference<T>::type&& RvalRef;
return static_cast<RvalRef>(a);
}
Copy the code
Remove_reference removes the reference attribute of the input parameter regardless of whether it is an lvalue or an rvalue. RvalRef is an rvalue type and the final return type is an rvalue reference.
3.4 Example
In practice, a temporary variable is passed as an input parameter to STD :: Move and the return value is passed to a function that accepts an rvalue type to ‘steal’ resources from the temporary variable. Note that once a temporary variable is’ stolen ‘, it cannot be read or written to, otherwise undefined behavior will occur.
#include <utility>
#include <iostream>
#include <string>
#include <vector>
void foo(const std::string& n)
{
std::cout << "lvalue" << std::endl;
}
void foo(std::string&& n)
{
std::cout << "rvalue" << std::endl;
}
void bar(a)
{
foo("hello"); // rvalue
std::string a = "world";
foo(a); // lvalue
foo(std::move(a)); // rvalue
}
int main(a)
{
std::vector<std::string> a = {"hello"."world"};
std::vector<std::string> b;
b.push_back("hello"); // Overhead: string copy construct
b.push_back(std::move(a[1])); // Overhead: string move construct (steal pointer from temporary variable a[1])
std::cout << "bsize: " << b.size() << std::endl;
for (std::string& x: b)
std::cout << x << std::endl;
bar(a);return 0;
}
Copy the code
4 std::forward
4.1 What?
STD :: Forward is used for perfect forwarding. So what is a perfect retweet? Perfect forwarding realizes the function of keeping the value attribute of a parameter during transmission, that is, if it is an lvalue, it will still be an lvalue after transmission, if it is an rvalue, it will still be an rvalue after transmission.
In simple terms, STD :: Move is used to strongly convert an lvalue or rvalue object to an rvalue semantics, while STD :: Forward is used to preserve both the lvalue semantics of an lvalue object and the rvalue semantics of an rvalue object.
4.2 according to?
#include <utility>
#include <iostream>
void bar(const int& x)
{
std::cout << "lvalue" << std::endl;
}
void bar(int&& x)
{
std::cout << "rvalue" << std::endl;
}
template <typename T>
void foo(T&& x)
{
bar(x);
}
int main(a)
{
int x = 10;
foo(x); // Output: lvalue
foo(10); // Output: lvalue
return 0;
}
Copy the code
Foo (x) and foo(10) both output lValue. Foo (x) outputs lvalue because x is an lvalue, but 10 is an rvalue. Why does foo(10) also output lvalue?
This is because 10 is only an rvalue argument to the function foo, but inside foo, 10 is substituted into the parameter x, which is a named variable, an rvalue, so bar(x) in foo still outputs lvalue.
So the question is, what if we want to preserve the rvalue semantics of x inside foo? STD :: Forward comes in handy.
Simply rewrite the foo function:
template <typename T>
void foo(T&& x)
{
bar(std::forward<T>(x));
}
Copy the code
4.3 How?
STD :: Forward sounds a bit magical, so how exactly does it work?
template<typename T, typename Arg>
shared_ptr<T> factory(Arg&& arg)
{
return shared_ptr<T>(new T(std::forward<Arg>(arg)));
}
template<class S>
S&& forward(typename remove_reference<S>::type& a) noexcept
{
return static_cast<S&&>(a);
}
X x;
factory<A>(x);
Copy the code
If the input argument to factory is an lvalue, then Arg = X&, STD ::forward
= X& according to the overlay rule. Therefore, in this case, STD :: Forward
(Arg) is still an lvalue.
Conversely, if the factory input argument is an rvalue, then Arg = X, STD ::forward
= X. In this case, STD :: Forward
(Arg) is an rvalue.
Just enough to preserve the semantics of lvalue or rvalue!
4.4 Example
Go straight to the code. If the previous understand, I believe that the output of this code can also guess a pretty close.
#include <utility>
#include <iostream>
void overloaded(const int& x)
{
std::cout << "[lvalue]" << std::endl;
}
void overloaded(int&& x)
{
std::cout << "[rvalue]" << std::endl;
}
template <class T> void fn(T&& x)
{
overloaded(x);
overloaded(std::forward<T>(x));
}
int main(a)
{
int i = 10;
overloaded(std::forward<int>(i));
overloaded(std::forward<int&>(i));
overloaded(std::forward<int&&>(i));
fn(i);
fn(std::move(i));
return 0;
}
Copy the code
Recommended reading
- STL source code analysis — Vector
- STL source code analysis – Hashtable
- STL source code analysis –algorithm
- Principles of the ZooKeeper Client
- Redis implements distributed locking
- Recommend a few useful efficiency devices
- Restrict the C/C++ keyword
For more exciting content, please scan the code to follow the wechat public number: back-end technology cabin. If you think this article is helpful to you, please share, forward, read more.