跳至主要內容

move移动语义和forward完美转发

张威大约 4 分钟c/c++对象优化

move移动语义和forward完美转发

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方法;

匹配右值的push_back函数:

我们在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()移动语义。。。由于我们知道,(直接使用回调用左值的construct函数,而不是右值的),所以我们在传参的时候,希望使用它的本身的右值语义,我们这里使用std::move()相当于将左值强制转换成右值引用类型。move()源码如下

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

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

template<typename Ty>
void push_bask(Ty&& val) {
    if(full()) {
        expand();
    }
   // forward完美转发,能复原val原本类型,即右值引用-》右值;左值引用-》左值
    _allocate.construct(_last++, std::forward<Ty>(val));
}

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

int main(void)
{
	CMyString str1 = "aaa";
	vector<CMyString> vec;
	
	cout << "----------------------------------" << endl;
	vec.push_back(str1);	//CMyString&
	vec.push_back(CMyString("nnn"));	//CMyString&&
	cout << "----------------------------------" << endl;
}

std::forward<Ty>(val)自动识别val是左值还是右值并返回对应类型

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

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

函数模板参数推演Ty是左值CMyString&& 还是 CMyString&
CMyString&& + && =CMyString&& 
CMyString& + && = CMyString&