Article directory
- 1. & amp; & amp; in the template-universal reference
- 2. Perfect forwarding and its application scenarios
- 3. Code used
-
- 3.1 string.h
- 3.2 list.h
- 3.3 test.cpp
1. & amp; & amp;-universal reference in templates
First let’s look at this piece of code:
There are 4 functions here. We can easily see that they have an overloaded relationship.
Then we give a function template like this
Let’s take a look at the parameters of this function template,T & amp; & amp; t
There are two & amp; & amp; here, so is it an rvalue reference?
No!
& amp; & amp; in the template does not represent an rvalue reference, but a universal reference, which can receive both lvalues and rvalues.
When we instantiate this function template
You can pass lvalues or rvalues.
What we pass is an lvalue, so the parameter t is an lvalue reference. What we pass is an rvalue, and the parameter t is an rvalue reference.
Therefore, it is also called reference folding in some places, that is, when we pass an lvalue, it seems to fold & amp; & amp;.
However, you can see that after we receive t here, it is passed down one layer
Then everyone needs to think about how t is passed down to Fun inside the PerfectForward function. What will t match if it is passed to Fun?
Then let’s run the program and see the results:
Eh! what’s up?
Why are all the matching lvalue references?
So why is this happening here?
Do you still remember that I mentioned something to you in the previous article:
That is,an rvalue cannot take an address, but giving an alias to an rvalue will cause the rvalue to be stored in a specific location, and the address of that location can be obtained.
For example: the address of literal 10 cannot be taken, but after rr1 is referenced, the address of rr1 can be taken.
address, you can also modify rr1.
If it can take addresses and assign values, wouldn’t it become an lvalue?
Therefore, After an rvalue is referenced by an rvalue, the attribute will become an lvalue
If you think about it, this design is actually reasonable:
For example, this scene
Transferring a resource can also be considered as modifying it, while the dying value of a temporary variable or anonymous object cannot be modified.
Then if we look back at our above questions, everyone should understand
Even if what we pass is an rvalue, it will become an lvalue after being referenced by an rvalue, so if it is passed to Fun, it will match an lvalue.
so:
The universal reference of the template only provides the ability to receive lvalues and rvalues at the same time. The function is to limit the types received, but it degenerates into lvalues in subsequent use.
But in some scenarios, we hope to maintain its lvalue or rvalue attributes during the transfer process, so how to do this?
You need to use the Perfect Forwarding we will learn below.
2. Perfect forwarding and its application scenarios
First, let’s look at a corresponding scenario:
We have simulated and implemented lists before, so make a copy
Some things that are no longer needed will be deleted.
In conjunction with the string we created ourselves, I will also comment out the move copy and move structures we added to the string.
I write a piece of code like this
Then our previous implementation did not have move semantics, so
All are deep copies, regardless of lvalue or rvalue.
Then we release the move construction and move copy of string, and then I add the rvalue reference version to push_back of list.
But our push_back reuses insert, so insert also adds an rvalue reference version.
Run again
Hey, why doesn’t it work? The push_back of the list is still a deep copy
What’s the problem?
, is it the problem we mentioned above?
An rvalue becomes an lvalue when it is referenced by an rvalue.
If the parameter passed to push_back for the first time is an rvalue, the rvalue reference version of push_back is called. However, when insert is called in push_back and passed for the second time, it becomes an lvalue.
So in the end, whether it is rvalue or lvalue push_back, the final inserted insert is the lvalue version, so it is a deep copy
So how to solve this problem?
How to maintain its lvalue or rvalue attributes during the transfer process?
This requires the use of Perfect forwarding
std::forward perfect forwarding retains the object’s native type attributes during the parameter transfer process
It is also a function template provided in the library.
Then we call forward directly to maintain the native attributes of the parameters.
Then let’s run it again
Oops, why not?
, because there is actually another layer of transmission below
When creating a new node in insert, the constructor of node must be called.
So we also need to add forward here.
but!
We only have the lvalue reference version of node’s constructor
So, we need to add another version of rvalue reference
Moreover, when initializing _data here, we also need to use forward to maintain the attributes of
Then this time
That’s it, push_back of rvalue is a mobile copy
Then we have the perfect forwarding of our initial scene:
All match the version referenced by rvalue
what to do?
Just add a perfect forwarding
3. Code used
3.1 string.h
#pragma once namespace bit {<!-- --> class string {<!-- --> public: typedef char* iterator; iterator begin() {<!-- --> return _str; } iterator end() {<!-- --> return _str + _size; } string(const char* str = "") :_size(strlen(str)) , _capacity(_size) {<!-- --> //cout << "string(char* str)" << endl; _str = new char[_capacity + 1]; strcpy(_str, str); } // s1.swap(s2) void swap(string & s) {<!-- --> std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } // copy construction string(const string & s) :_str(nullptr) {<!-- --> cout << "string(const string & amp; s) -- deep copy" << endl; string tmp(s._str); swap(tmp); } // move constructor string(string & amp; & amp; s) :_str(nullptr) {<!-- --> cout << "string(string & amp; & amp; s) -- move copy" << endl; swap(s); } // Assignment overloading string & amp; operator=(const string & amp; s) {<!-- --> cout << "string & amp; operator=(string s) -- deep copy" << endl; string tmp(s); swap(tmp); return *this; } // move assignment string & amp; operator=(string & amp; & amp; s) {<!-- --> cout << "string & amp; operator=(string & amp; & amp; s) -- move assignment" << endl; swap(s); return *this; } ~string() {<!-- --> delete[] _str; _str = nullptr; } char & operator[](size_t pos) {<!-- --> assert(pos < _size); return _str[pos]; } void reserve(size_t n) {<!-- --> if (n > _capacity) {<!-- --> char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } } void push_back(char ch) {<!-- --> if (_size >= _capacity) {<!-- --> size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2; reserve(newcapacity); } _str[_size] = ch; + + _size; _str[_size] = '\0'; } //string operator + =(char ch) string & operator + =(char ch) {<!-- --> push_back(ch); return *this; } string operator + (char ch) {<!-- --> string tmp(*this); tmp + = ch; return tmp; } const char* c_str() const {<!-- --> return _str; } private: char* _str; size_t _size; size_t _capacity; // Does not include the last marked \0 }; string to_string(int value) {<!-- --> bool flag = true; if (value < 0) {<!-- --> flag = false; value = 0 - value; } string str; while (value > 0) {<!-- --> int x = value % 10; value /= 10; str + = ('0' + x); } if (flag == false) {<!-- --> str + = '-'; } std::reverse(str.begin(), str.end()); return str; } }
3.2 list.h
#pragma once #include<assert.h> namespace yin {<!-- --> template<class T> struct list_node {<!-- --> list_node<T>* _next; list_node<T>* _prev; T_data; list_node(const T & amp; x = T()) :_next(nullptr) , _prev(nullptr) , _data(x) {<!-- -->} list_node(T & amp; & amp; x = T()) :_next(nullptr) , _prev(nullptr) , _data(forward<T>(x)) {<!-- -->} }; // 1. The iterator is either a native pointer // 2. Iterators are either encapsulation of native pointers by custom types, simulating the behavior of pointers. template<class T, class Ref, class Ptr> struct __list_iterator {<!-- --> typedef list_node<T> node; typedef __list_iterator<T, Ref, Ptr> self; node* _node; __list_iterator(node* n) :_node(n) {<!-- -->} Ref operator*() {<!-- --> return _node->_data; } Ptr operator->() {<!-- --> return &_node->_data; } self & operator + + () {<!-- --> _node = _node->_next; return *this; } self operator + + (int) {<!-- --> self tmp(*this); _node = _node->_next; return tmp; } self & operator--() {<!-- --> _node = _node->_prev; return *this; } self operator--(int) {<!-- --> self tmp(*this); _node = _node->_prev; return tmp; } bool operator!=(const self & amp; s) {<!-- --> return _node != s._node; } bool operator==(const self & s) {<!-- --> return _node == s._node; } }; template<class T> class list {<!-- --> typedef list_node<T> node; public: typedef __list_iterator<T, T & amp;, T*> iterator; typedef __list_iterator<T, const T & amp;, const T*> const_iterator; iterator begin() {<!-- --> //iterator it(_head->_next); //return it; return iterator(_head->_next); } const_iterator begin() const {<!-- --> return const_iterator(_head->_next); } iterator end() {<!-- --> return iterator(_head); } const_iterator end() const {<!-- --> //iterator it(_head->_next); //return it; return const_iterator(_head); } void empty_init() {<!-- --> _head = new node(T()); _head->_next = _head; _head->_prev = _head; } list() {<!-- --> empty_init(); } template <class Iterator> list(Iterator first, Iterator last) {<!-- --> empty_init(); while (first != last) {<!-- --> push_back(*first); + + first; } } void swap(list<T> & amp; tmp) {<!-- --> std::swap(_head, tmp._head); } list(const list<T> & lt) {<!-- --> empty_init(); list<T> tmp(lt.begin(), lt.end()); swap(tmp); } // lt1 = lt3 list<T> & operator=(list<T> lt) {<!-- --> swap(lt); return *this; } ~list() {<!-- --> clear(); delete _head; _head = nullptr; } void clear() {<!-- --> iterator it = begin(); while (it != end()) {<!-- --> //it = erase(it); erase(it + + ); } } void push_back(const T & x) {<!-- --> insert(end(), x); } void push_back(T & amp; & amp; x) {<!-- --> insert(end(), forward<T>(x)); } void push_front(const T & x) {<!-- --> insert(begin(), x); } void pop_back() {<!-- --> erase(--end()); } void pop_front() {<!-- --> erase(begin()); } void insert(iterator pos, const T & amp; x) {<!-- --> node* cur = pos._node; node* prev = cur->_prev; node* new_node = new node(x); prev->_next = new_node; new_node->_prev = prev; new_node->_next = cur; cur->_prev = new_node; } void insert(iterator pos, T & amp; & amp; x) {<!-- --> node* cur = pos._node; node* prev = cur->_prev; node* new_node = new node(forward<T>(x)); prev->_next = new_node; new_node->_prev = prev; new_node->_next = cur; cur->_prev = new_node; } iterator erase(iterator pos) {<!-- --> assert(pos != end()); node* prev = pos._node->_prev; node* next = pos._node->_next; prev->_next = next; next->_prev = prev; delete pos._node; return iterator(next); } private: node* _head; }; }
3.3 test.cpp
void Fun(int & amp; x) {<!-- --> cout << "lvalue reference" << endl; } void Fun(const int & amp; x) {<!-- --> cout << "const lvalue reference" << endl; } void Fun(int & amp; & amp; x) {<!-- --> cout << "rvalue reference" << endl; } void Fun(const int & amp; & amp; x) {<!-- --> cout << "const rvalue reference" << endl; } template<typename T> void PerfectForward(T & amp; & amp; t) {<!-- --> Fun(forward<T>(t)); } int main() {<!-- --> PerfectForward(10); // rvalue int a; PerfectForward(a); // lvalue PerfectForward(move(a));// rvalue const int b = 8; PerfectForward(b);// const lvalue PerfectForward(move(b)); // const rvalue return 0; } //#include "list.h" // //int main() //{<!-- --> // yin::list<bit::string> lt; // // bit::string s1("hello world"); //lt.push_back(s1); // // lt.push_back(bit::string("hello world")); // lt.push_back("hello world"); // // return 0; //}