在看文章前需要明确一点,引用的重叠:
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
强转,一个函数写两个版本.
完美转发实现了参数在传递过程中保持其值属性的功能,即若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值。