【C++】move移动语义和forward类型完美转发

问题的引出

现在又如下代码:(自定义的String的类)

class String
{
public:
	String(const char *str = nullptr)
	{
		cout << "String()" << endl;
		if (str == nullptr)
		{
			_ptr = new char[1];
			_ptr = '\0';
		}
		else
		{
			_ptr = new char[strlen(str) + 1];
			strcpy(_ptr, str);
		}
	}
	~String()
	{
		cout << "~String()" << endl;
		delete[]_ptr;
		_ptr = nullptr;
	}
	String(const String &obj)
	{
		cout << "String(const String &obj)" << endl;
		_ptr = new char[strlen(obj._ptr) + 1];
		strcpy(_ptr, obj._ptr);
	}
	String(String &&obj)
	{
		cout << "String(const String &&obj)" << endl;
		this->_ptr = obj._ptr;
		obj._ptr = nullptr;
	}
	String &operator=(const String &obj)
	{
		cout << "operator=(const String &obj)" << endl;
		if (this == &obj)
		{
			return *this;
		}
		delete[] _ptr;

		_ptr = new char[strlen(obj._ptr) + 1];
		strcpy(_ptr, obj._ptr);
	}
	String &operator=(String &&obj)
	{
		cout << "operator=(const String &&obj)" << endl;
		this->_ptr = obj._ptr;
		obj._ptr = nullptr;
		return *this;
	}
private:
	char *_ptr;
};

main方法的调用

int main()
{
	vector<String> vec;
	vec.reserve(10);
	String str1 = "hello";
	cout << "---------------------" << endl;
	vec.push_back(str1);
	vec.push_back(String("world"));
	cout << "--------------------------" << endl;
	return 0;
}

结果:

那么vector是怎么做到的呢?它怎么知道使用String的左值引用的拷贝构造还是使用String的右值拷贝构造?


我们首先实现自己的vector容器。

//容器空间适配器
template<typename T>
struct Allocator
{
	T *allocate(size_t size)//负责内存的开辟
	{
		return (T*)malloc(sizeof(T)*size);
	}
	void deallocate(void *p)//负责内存的释放
	{
		free(p);
	}
	void construct(T *p, const T &val)//负责对象的构造
	{
		new(p) T(val);
	}
	void destroy(T *p)//负责对象的析构
	{
		p->~T();
	}
};
template<typename T,typename Alloc = Allocator<T>>
class vector
{
public:
	vector(size_t size = 10)
	{
		_first = _allocator.allocate(size);//只给数组开辟空间,不进行构造
		_last = _first;
		_end = _first + size;
	}
	~vector()
	{
		T *ptmp = _first;
		for (; ptmp != _last; ++ptmp)//首先释放数组中的有效元素
		{
			_allocator.destroy(ptmp);
		}
		//释放数组
		_allocator.deallocate(_first);
		_first = _end = _last = nullptr;
	}
	vector(const vector&src)
	{
		//首先申请空间
		size_t size = src._end - src._first;//获取src的数组长度
		_first = _allocator.allocate(size);//申请空间
		_last = _first;
		int len = src._last - src._first;
		for (int i = 0; i < len; i++)//拷贝数据
		{
			_allocator.construct(_last++, src._first[i]);
		}
		_end = _first + size;
	}
	void operator=(const vector&src)
	{
		//1,判断是否为自赋值
		if (this == &src)
			return;
		//2,释放原来对象占用的空间
		~vector();
		//3,和拷贝构造过程一样
		size_t size = src._end - src._first;//获取src的数组长度
		_first = _allocator.allocate(size);//申请空间
		_last = _first;
		int len = src._last - src._first;
		for (int i = 0; i < len; i++)
		{
			_allocator.construct(_last++, src._first[i]);
		}
		_end = _first + size;
	}
	void push_back(const T &val)
	{
		if (full())
			expand();
		_allocator.construct(_last++, val);
	}
	void pop_back()
	{
		if (empty())
			return;
		--_last;
		_allocator.destroy(_last);
	}
	T back()
	{
		if (empty())
			throw "the vector is empty";
		return *(_last - 1);
	}
	bool full()const { return _last == _end; }
	bool empty()const { return _first == _last; }
	size_t size()const { return _last - _first; }

private:
	T *_first;//指向数组的首元素的地址
	T *_last;//指向数据最后一个有效元素的后继
	T *_end;//指向数组有效空间最后一个元素的后继
	Alloc _allocator;//空间适配器
	void expand()//二倍扩容
	{
		size_t len = _end - _first;//当前的数组大小
		size_t size = len * 2;//扩容之后的数组的大小
		
		T *ptmp = _allocator.allocate(size);//先申请大小为原来二倍的空间
		T *pcur = _first;//
		for (int i = 0; i < len; i++)
			_allocator.construct(ptmp + i, *pcur++);
		for (int i = 0; i < len; i++)
			_allocator.destroy(_first++);
		_allocator.deallocate(_first);
		_first = ptmp;
		_end = _first + size;
		_last = _first + len;
	}
};

我们在vector类里面增加一个带右值引用的push_back的重载函数,

在空间配置器里面增加一个带右值引用的construct的方法。

void push_back(T &&val)
	{
		if (full())
			expand();
		_allocator.construct(_last++, std::move(val));
	}
void construct(T *p, T &&val)//负责对象的构造
	{
		new(p) T(std::move(val));
	}

上面用到了方法std::move()移动语义。。。由于我们知道,右值引用本身是一个左值,所以我们在传参的时候,希望使用它的本身的右值语义,我们这里使用move进行了移动语义将val从左值引用转化位右值引用类型

测试结果:


但是上面的方法不免显得有点繁琐了,因为一旦我们要使用val的右值引用就必须给它类型转化。

 

我们可以使用函数模板来简化我们的代码量。

template<typename Ty>
void push_back(Ty &&val)
{
	if (full())
		expand();
	_allocator.construct(_last++, std::forward<Ty>(val));
}

std::forward(Ty)(val)自动识别val是左值还是右值。

根据函数模板参数推演+引用折叠我们可以使用这个方法很好的解决右值引用本身是左值的问题。

引用折叠:
右值引用+右值引用=右值引用

右值引用+左值引用=左值引用

 

同理我们的空间配置也可以这样实现

template<typename Ty>
void construct(T *p, Ty &&val)//负责对象的构造
{
	new(p) T(std::forward<Ty>(val));
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值