在看文章前需要明確一點,引用的重疊:
template <typename T>
void Func(T &&val){//....}
1.當實例化的T的爲一個左值引用時,即 T& ++ &&,最終val是一個左值;
2.當實例化的T的爲一個右值引用時,即 T&& ++ &&,最終val是一個右值;
string和vector類的代碼:
#include <iostream>
#include <string.h>
using namespace std;
#include<iostream>
#include <string.h>
using namespace std;
//自定義string類
class String
{
public:
//構造函數
String(const char *ptr = nullptr){
std::cout << "String(const char *ptr)" << std::endl;
if (ptr == nullptr){
mpstr = new char[1];
mpstr[0] = '\0';
}else{
mpstr = new char[strlen(ptr) + 1];
strcpy(mpstr, ptr);
}
}
//左值引用參數的拷貝構造函數
String(const String &src){ //src引用的是一個左值
std::cout << "String(const String &src)" << std::endl;
mpstr = new char[strlen(src.mpstr) + 1];
strcpy(mpstr, src.mpstr);
}
//右值引用參數的拷貝構造函數
String(String &&src){//src引用的是一個臨時對象
std::cout << "String(const String &&src)" << std::endl;
mpstr = src.mpstr;
src.mpstr = nullptr;
}
//左值引用參數賦值運算符的重載函數
String& operator=(const String &src){//src引用的是一個左值
std::cout << "String& operator=(const String &src)" << std::endl;
if (this == &src)
return *this;
delete[]mpstr;
mpstr = new char[strlen(src.mpstr) + 1];
strcpy(mpstr, src.mpstr);
return *this;
}
//右值引用參數賦值運算符的重載函數
String& operator=(String &&src){ //src引用的是一個臨時對象
std::cout << "String& operator=(const String &&src)" << std::endl;
if (this == &src)
return *this;
delete[]mpstr;
mpstr = src.mpstr;
src.mpstr = nullptr;
return *this;
}
~String(){
std::cout << "~String()" << std::endl;
if (mpstr != nullptr){
delete mpstr;
mpstr = nullptr;
}
}
bool operator>(const String &src){
if (strcmp(mpstr, src.mpstr) > 0)
return true;
return false;
}
bool operator<(const String &src){
if (strcmp(mpstr, src.mpstr) < 0)
return true;
return false;
}
bool operator==(const String &src){
if (strcmp(mpstr, src.mpstr) == 0)
return true;
return false;
}
//獲取字符串的長度
int length()const{ return strlen(mpstr); }
//根據下標返回對應的字符
char& operator[](int index){ return mpstr[index]; }
//返回該字符串
const char* c_str()const{ return mpstr; }
private:
char *mpstr;
friend String operator+(const String &lhs, const String &rhs);
friend ostream& operator<<(ostream &out, const String &src);
};
//operator+
String operator+(const String &lhs, const String &rhs){
String str;
char* temp = new char[lhs.length() + rhs.length() + 1];
strcpy(str.mpstr, lhs.mpstr);
strcat(str.mpstr, rhs.mpstr);
return str;
}
ostream& operator<<(ostream &out, const String &src){
out << src.mpstr;
return out;
}
//vector的空間配置器 Allocator
template<typename T>
struct Allocator{
T*allocate(size_t size){ //負責開闢內存
return (T*)malloc(sizeof(T)*size);
}
void deallocate(void *p){//負責內存的釋放
free(p);
}
//帶左值參數的construct
void construct(T*p,const T&val){//負責在指定位置構造對象
new (p) T(val);//定位new
}
//帶右值參數的construct
void construct(T*p,T&&val){//負責在指定位置構造對象
new (p) T(std::move(val));//定位new
}
void destroy(T*p){ //負責指定對象p的析構,類型爲T
p->~T();
}
};
//自定義vector類
template <typename T, typename Alloca = Allocator<T>>
class Vector
{
public:
//默認構造的vector,底層沒分配過內存0
Vector() :mpVec(NULL), mSize(0), mCur(0){}
//size表示初始的內存大小,val表示內存初始值
Vector(int size, const T &val = T())
:mSize(size), mCur(size){
mpVec = _allocator.allocate(mSize * sizeof(T));
for (int i = 0; i < mSize; ++i){
_allocator.construct(mpVec + i, val);
}
}
//帶左值參數的拷貝構造
Vector(const Vector &src)
:mSize(src.mSize), mCur(src.mCur){
mpVec = _allocator.allocate(sizeof(T)*mSize);
for (int i = 0; i < mCur; ++i){
_allocator.construct(mpVec + i, src.mpVec[i]);
}
}
//operator=
Vector& operator=(const Vector &src){
if (this == &src)
return *this;
for (int i = 0; i < mCur; ++i){
_allocator.destroy(mpVec + i);
}
_allocator.deallocate(mpVec);
mpVec = _allocator.allocate(sizeof(T)*mSize);
for (int i = 0; i < mCur; ++i){
_allocator.construct(mpVec + i, src.mpVec[i]);
}
return *this;
}
~Vector(){
for (int i = 0; i < mCur; ++i){
_allocator.destroy(mpVec + i);
}
_allocator.deallocate(mpVec);
mpVec = NULL;
}
//帶左值參數的末尾添加元素
void push_back(const T &val) {
std::cout<<"push_back(const T &val)"<<std::endl;
if (full())
reSize();
_allocator.construct(mpVec + mCur, val);
mCur++;
}
//帶右值參數的末尾添加元素
void push_back(T &&val) {
std::cout<<"push_back(T &&val)"<<std::endl;
if (full())
reSize();
_allocator.construct(mpVec + mCur, std::move(val));
mCur++;
}
//末尾刪除
void pop_back(){
if (empty())
return;
--mCur;
_allocator.destroy(mpVec + mCur);
}
T front()const{ return mpVec[0]; }
T back()const{ return mpVec[mCur - 1]; }
bool full()const{ return mCur == mSize; }
bool empty()const{ return mCur == 0; }
T& operator[](int index){ return mpVec[index]; }
//內存以2倍方式增長
void reSize(){
//默認的size==0
if (mSize == 0){
mpVec = _allocator.allocate(sizeof(T));mSize = 1;mCur = 0;
}else{
T *ptmp = _allocator.allocate(mSize * 2 * sizeof(T));
for (int i = 0; i < mCur; ++i){
_allocator.construct(ptmp + i, mpVec[i]);
}
mSize *= 2;
for (int i = 0; i < mCur; ++i){
_allocator.destroy(mpVec + i);
}
_allocator.deallocate(mpVec);
mpVec = ptmp;
}
}
int size()const{ return mCur; }
//定義當前容器的迭代器類型 ,遍歷容器(遍歷容器底層的數據結構)
class iterator
{
public:
iterator(T *p = NULL){
ptr = p;
}
bool operator!=(const iterator &it){
return ptr != it.ptr;
}
int operator-(const iterator &it){
return ptr - it.ptr;
}
void operator++(){
++ptr;
}
T& operator*(){
return *ptr;
}
private:
T *ptr; //本質上就是一根被泛化的指針
};
iterator begin(){ return iterator(mpVec); }
iterator end(){ return iterator(mpVec + mCur); }
private:
T *mpVec;//動態數組的起始地址,相當於first
int mSize;//容量
int mCur;//當前size
Alloca _allocator;//空間配置器對象
};
int main(){
String str1 = "123";
Vector<String> vec1;
Vector<String> vec2;
Vector<String> vec3;
vec1.push_back(str1);
vec2.push_back(std::move(str1));
vec3.push_back(String("xxxx"));
return 0;
}
我們將代碼中vector的push_back和Allocator的construct取出來說明 std::move 的作用:
//帶左值參數的末尾添加元素
void push_back(const T &val) {
if (full())
reSize();
_allocator.construct(mpVec + mCur, val);
mCur++;
}
//帶右值參數的末尾添加元素
void push_back(T &&val) {
std::cout<<"push_back(T &&val)"<<std::endl;
if (full())
reSize();
_allocator.construct(mpVec + mCur, std::move(val));
/*
val接收的是一個右值,但是在當前函數本身是一個左值,所以,我們想
調用配置器的帶右值引用的construct,在這裏調用String的帶右值的拷貝構造函數,
需要將val強轉成一個右值進行傳遞,
所以,使用std::move進行轉換,不管val是左值還是右值,得到的都是一個右值。
*/
mCur++;
}
總結:
std::move
將一個值,無論是左值還是右值都強轉爲右值。但是出現一個問題,這樣的代碼我們每次都要去實現一個帶右值參數的,一個帶左值參數的,效率並不高,接下來將引出std::forward,將最初傳進來的右值完美的轉發,保證不會變爲左值,不需要我們進行強轉。
std::forward:
template <typename Ty>
void push_back(Ty &&val){
std::cout<<"template push_back"<<std::endl;
if (full())
reSize();
_allocator.construct(mpVec + mCur, std::forward(val));
mCur++;
}
template <typename Ty>
void construct(T*p,Ty&&val){
std::cout<<"template construct"<<std::endl;
new (p) T(std::forward<Ty>(val));//定位new
}
運行結果:
String(const char *ptr)
template push_back
template construct
String(const String &src)
template push_back
template construct
String(const String &&src)
String(const char *ptr)
template push_back
template construct
String(const String &&src)
~String()
~String()
~String()
~String()
~String()
std::forward總結:
- 不過不加
std::forward
,在push_back中,val是一個左值,其實我們想給construct中傳入一個右值; - 加上
std::forward
,儘管val在函數push_back中是一個左值,但是std::forward
能知道,能夠識別,調用者傳給push_back的一開始就是個右值,所以會把val是右值的類型完美的轉發傳達給construct,就不需要我們手動去調用std::move
強轉,一個函數寫兩個版本.
完美轉發實現了參數在傳遞過程中保持其值屬性的功能,即若是左值,則傳遞之後仍然是左值,若是右值,則傳遞之後仍然是右值。