案例引入
首先我们看一个案例。
我们使用自己定义的string类:
class CMyString {
public:
CMyString(const char* str = nullptr) {
cout << "CMyString(const char*)" << endl;
if (str != nullptr) {
mptr = new char[strlen(str) + 1];
strcpy(mptr, str);
} else {
mptr = new char[1];
*mptr = '\0';
}
}
~CMyString() {
cout << "~CMyString()" << endl;
delete[] mptr;
mptr = nullptr;
}
CMyString(const CMyString& str) {
cout << "CMyString(const CMyString&)" << endl;
mptr = new char[strlen(str.mptr) + 1];
strcpy(mptr, str.mptr);
}
CMyString(CMyString &&str) { //str引用的就是一个右值
cout << "CMyString(CMyString &&str)" << endl;
mptr = str.mptr;
str.mptr = nullptr;
}
CMyString& operator=(const CMyString& str) {
cout << "operator=(const CMyString&)" << endl;
if (this == &str) {
return *this;
}
delete[] mptr;
mptr = new char[strlen(str.mptr) + 1];
strcpy(mptr, str.mptr);
return *this;
}
CMyString& operator=(CMyString &&str) {
cout << "operator=(CMyString&&)" << endl;
if (this == &str)
return *this;
delete[] mptr;
mptr = str.mptr;
str.mptr = nullptr;
return *this;
}
const char* c_str() const { return mptr; }
private:
char* mptr;
friend CMyString operator+(const CMyString &lhs,
const CMyString &rhs);
friend ostream& operator<<(ostream &out, const CMyString &str);
};
CMyString operator+(const CMyString &lhs,
const CMyString &rhs){
//char *ptmp = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
CMyString tempStr;
tempStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
strcpy(tempStr.mptr, lhs.mptr);
strcat(tempStr.mptr, rhs.mptr);
//delete []ptmp;
return tempStr;
//return CMyString(ptmp);
}
ostream& operator<<(ostream &out, const CMyString &str) {
out << str.mptr;
return out;
}
现在我们做如下测试:
int main () {
CMyString str1 = "aaa";
vector<CMyString> vec;
vec.reserve(10);
cout << "-----------------------" << endl;
vec.push_back(str1); //传了一个左值,引用的是普通的带左值引用的拷贝构造
vec.push_back(CMyString("bbb")); //传了一个右值,引用的是一个带右值引用的拷贝构造
cout << "-----------------------" << endl;
return 0;
}
打印结果如下:
CMyString(const char*) //str1构造
-----------------------
CMyString(const CMyString&)//str1拷贝构造
CMyString(const char*) //CMyString("bbb")是一个右值,底层先构造实例
CMyString(CMyString &&str) //然后调用移动构造
~CMyString() //释放CMyString("bbb"),但其实他的底层已经指向空了
-----------------------
~CMyString()
~CMyString()
~CMyString()
其实我们的移动构造是没有什么开销的,我们也是先构造出CMyString("bbb")
的对象实例,然后把实例的资源直接给到vec的底层元素,这又是我们移动构造做的事情。随后的析构函数也没有什么开销,因为我们已经把CMyString("bbb")
实例的底层指针置为空了。
所以不要认为我们把一个类对象传入容器的开销有多大,其实根本就不打,从C++11开始,push_back的底层就已经很高效了。
但是我们还是要问一个问题:
push_back是如何做到的呢?
move 移动语义和类型的完美转发
重新审视自己实现的vector类
在之前的文章中,我们自己实现了一个简单的vector,现在一起改造他!实现移动构造函数和类型的完美转发。
//以下代码是带有空间配置器、迭代器内置类型的vector版本
template <typename T, typename Alloc = Allocator<T> >
class vector {
public:
vector(int size = 10) {
//_first = new T[size];
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~vector() {
//delete[] _first;
for(T *p = _first; p != _last; ++p) {
_allocator.destroy(p); //把_first指针指向的数组的有效元素进行析构操作
}
_allocator.deallocate(_first); //释放堆上的内存
_first = _last = _end =nullptr;
}
vector(const vector<T> &rhs) {
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i) {
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator=(const vector<T> &rhs) {
if (this == rhs) return *this;
//delete[]_first;
for(T *p = _first; p != _last; ++p) {
_allocator.destroy(p); //把_first指针指向的数组的有效元素进行析构操作
}
_allocator.deallocate(_first); //释放堆上的内存
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i) {
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T &val) {
if (full()) expand();
//*_last++ = val; _last指针指向的内存构造一个值为val的对象
_allocator.construct(_last, val);
_last++;
}
void pop_back() {
if (empty()) return ;
//--_last; 不仅要把_last--,还需要析构删除的元素
--_last;
_allocator.destroy(_last);
}
T back() const { //返回容器末尾元素的值
return *(_last - 1);
}
bool full() const { return _last == _end; }
bool empty() const { return _first == _last; }
int size() const { return _last - _first; }
T& operator[](int index) { // vec[2]
if (index < 0 || index >= size()) {
throw "OutOfRangeException";
}
return _first[index];
}
//迭代器
class iterator {
public:
iterator(T *ptr = nullptr) : _ptr(ptr) {}
bool operator!=(const iterator &it) const {
return _ptr != it._ptr;
}
void operator++() {
_ptr++;
}
T& operator*() { return *_ptr; }
const T& operator*() const { return *_ptr; }
private:
T *_ptr;
};
//需要给容器提供begin和end方法
iterator begin() { return iterator(_first); }
iterator end() { return iterator(_last); }
private:
T *_first; //指向数组起始的位置
T *_last; //指向数组中有效元素的后继位置
T *_end; //指向数组空间的后继位置
Alloc _allocator; //定义容器的空间配置器对象
void expand() { //容器的二倍扩容操作
int size = _end - _first;
//T *ptemp = new T[2 * size];
T *ptemp = _allocator.allocate(2 * size);
for (int i = 0; i < size; ++i) {
//ptemp[i] = _first[i];
_allocator.construct(ptemp + i, _first[i]);
}
//delete[] _first;
for (T *p = _first; p != _last; ++p) {
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = ptemp;
_last = _first + size;
_end = _first + 2 * size;
}
};
在这个代码中,我们并没有实现带右值引用的拷贝构造和赋值构造,可以自行实现,我们在这里仅仅考虑push_back。
重载带右值引用的push_back
void push_back(T &&val) {
if (full()) expand();
//*_last++ = val; _last指针指向的内存构造一个值为val的对象
_allocator.construct(_last, val);
_last++;
}
首先右值引用的push_back和左值的一样。注意这里有一个_allocator.construct(_last, val);
他是负责对象构造的,所以我们必须给它也提供一个右值版本。
首先分析之前我们实现的空间配置器,construct:
void construct(T *p, T &&val) {
new (p) T(val);
}
然后,首先我们需要明确,一个右值引用变量本身还是一个左值。尽管所以我们上面的右值版本的push_back(T &&val),函数体中val还是一个左值,所以我们这段_allocator.construct(_last, val);还是会调用左值版本的construct,所以应该:
_allocator.construct(_last, std::move(val));
使用std::move(),将一个左值引用类型强转成右值引用类型。
同理在construct函数中写:
new (p) T(std::move(val));
完成上述工作后,
现在打印结果如下:
CMyString(const char*)
-----------------------
CMyString(const CMyString&)
CMyString(const char*)
CMyString(CMyString &&str)
~CMyString()
-----------------------
~CMyString()
~CMyString()
~CMyString()
已经与标准库的vector打印一致了。
NOTE:
但是我们这里仍然有很多问题,我们尽管提供了相应方法的右值版本,但是由于语法规则,右值引用变量其实是一个左值,所以在函数体内仍然无法分辨出该变量是一个右值引用,我们不得不添加std::move移动语义来把变量转换为一个右值。
那么这种问题应该如何解决呢?
使用forword类型完美转发实现
template<typename Ty>
void push_back(Ty &&val) {
if (full()) expand();
_allocator.construct(_last, val);
_last++;
}
刚才实现的两个push_back全部不要来,我们使用一个模版函数来定义push_back,如上述。
如果我们传参是一个左值引用,就有Ty=CMyString&
从Ty &&val==> CMyString& &&val ==> CMyString &val!
参数被编译器推导为了左值!
如果我们传参是一个右值引用,就有Ty=CMyString&&
从Ty &&val==> CMyString&& &&val ==> CMyString &&val!
参数被编译器推导为了右值!
这是由于以下几点:
- 引用折叠 &&&出现三个引用的概念叫引用折叠,是在C++11新出的
- 函数模版的类型推演
通过一个函数模版,把我们之前写的两个push_back方法可以完美替换掉;
那么我们解决下一个问题:函数体内无法分清变量到底是一个左值还是右值,因为右值引用本质上还是左值。
//forward类型的完美转发:
_allocator.construct(_last, std::forward<Ty>(val));
std::forward可以根据我们参数val本身的定义,推导出我们的val到底是一个左值还是右值。如果是左值,他就会返回一个左值类型,就会调用construct的左值版本;如果是右值,他就会返回一个右值类型,调用construct的右值版本。
最后同理,我们把construct代码也重构一下:
template<typename Ty>
void construct(T *p, Ty &&val) { //左值版本的构造
new (p) T(std::forward<Ty>(val));
}
一定要注意,我们定义的Ty只是来判断我们传进来的val是左值还是右值。
至此完成!
我们写了一个高效的push_back版本!
整体代码:
CMyString和MyVector代码
#include <iostream>
using namespace std;
class CMyString {
public:
CMyString(const char* str = nullptr) {
cout << "CMyString(const char*)" << endl;
if (str != nullptr) {
mptr = new char[strlen(str) + 1];
strcpy(mptr, str);
} else {
mptr = new char[1];
*mptr = '\0';
}
}
~CMyString() {
cout << "~CMyString()" << endl;
delete[] mptr;
mptr = nullptr;
}
//带左值引用参数的拷贝构造
CMyString(const CMyString& str) {
cout << "CMyString(const CMyString&)" << endl;
mptr = new char[strlen(str.mptr) + 1];
strcpy(mptr, str.mptr);
}
//带右值引用参数的拷贝构造
CMyString(CMyString &&str) { //str引用的就是一个右值
cout << "CMyString(CMyString &&str)" << endl;
mptr = str.mptr;
str.mptr = nullptr;
}
//带左值引用参数的赋值重载函数
CMyString& operator=(const CMyString& str) {
cout << "operator=(const CMyString&)" << endl;
if (this == &str) {
return *this;
}
delete[] mptr;
mptr = new char[strlen(str.mptr) + 1];
strcpy(mptr, str.mptr);
return *this;
}
//带右值引用参数的赋值重载函数
CMyString& operator=(CMyString &&str) {
cout << "operator=(CMyString&&)" << endl;
if (this == &str)
return *this;
delete[] mptr;
mptr = str.mptr;
str.mptr = nullptr;
return *this;
}
const char* c_str() const { return mptr; }
private:
char* mptr;
friend CMyString operator+(const CMyString &lhs,
const CMyString &rhs);
friend ostream& operator<<(ostream &out, const CMyString &str);
};
CMyString operator+(const CMyString &lhs,
const CMyString &rhs){
//char *ptmp = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
CMyString tempStr;
tempStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
strcpy(tempStr.mptr, lhs.mptr);
strcat(tempStr.mptr, rhs.mptr);
//delete []ptmp;
return tempStr;
//return CMyString(ptmp);
}
ostream& operator<<(ostream &out, const CMyString &str) {
out << str.mptr;
return out;
}
template<typename T>
struct Allocator {
T* allocate(size_t size) {//只负责内存开辟
return (T*)malloc(size * sizeof(T));
}
void deallocate(void *p) { // 负责内存释放
free(p);
}
// void construct(T *p, const T &val) {//复杂对象构造
// new (p) T(val);
// }
// void construct(T *p, T &&val) { //左值版本的构造
// new (p) T(std::move(val));
// }
template<typename Ty>
void construct(T *p, Ty &&val) { //左值版本的构造
new (p) T(std::forward<Ty>(val));
}
void destroy(T *p) { //负责对象析构
p->~T(); // ~T()代表了T类型的析构函数
}
};
template <typename T, typename Alloc = Allocator<T> >
class vector {
public:
vector(int size = 10) {
//_first = new T[size];
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~vector() {
//delete[] _first;
for(T *p = _first; p != _last; ++p) {
_allocator.destroy(p); //把_first指针指向的数组的有效元素进行析构操作
}
_allocator.deallocate(_first); //释放堆上的内存
_first = _last = _end =nullptr;
}
vector(const vector<T> &rhs) {
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i) {
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator=(const vector<T> &rhs) {
if (this == rhs) return *this;
//delete[]_first;
for(T *p = _first; p != _last; ++p) {
_allocator.destroy(p); //把_first指针指向的数组的有效元素进行析构操作
}
_allocator.deallocate(_first); //释放堆上的内存
int size = rhs._end - rhs._first;
//_first = new T[size];
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i) {
//_first[i] = rhs._first[i];
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
void pop_back() {
if (empty()) return ;
//--_last; 不仅要把_last--,还需要析构删除的元素
--_last;
_allocator.destroy(_last);
}
T back() const { //返回容器末尾元素的值
return *(_last - 1);
}
bool full() const { return _last == _end; }
bool empty() const { return _first == _last; }
int size() const { return _last - _first; }
T& operator[](int index) { // vec[2]
if (index < 0 || index >= size()) {
throw "OutOfRangeException";
}
return _first[index];
}
//迭代器
class iterator {
public:
iterator(T *ptr = nullptr) : _ptr(ptr) {}
bool operator!=(const iterator &it) const {
return _ptr != it._ptr;
}
void operator++() {
_ptr++;
}
T& operator*() { return *_ptr; }
const T& operator*() const { return *_ptr; }
private:
T *_ptr;
};
//需要给容器提供begin和end方法
iterator begin() { return iterator(_first); }
iterator end() { return iterator(_last); }
//
/*
void push_back(const T &val) {
if (full()) expand();
//*_last++ = val; _last指针指向的内存构造一个值为val的对象
_allocator.construct(_last, val);
_last++;
}
void push_back(T &&val) {
if (full()) expand();
_allocator.construct(_last, std::move(val));
_last++;
}*/
template<typename Ty>
void push_back(Ty &&val) {
if (full()) expand();
_allocator.construct(_last, std::forward<Ty>(val));
_last++;
}
private:
T *_first; //指向数组起始的位置
T *_last; //指向数组中有效元素的后继位置
T *_end; //指向数组空间的后继位置
Alloc _allocator; //定义容器的空间配置器对象
void expand() { //容器的二倍扩容操作
int size = _end - _first;
//T *ptemp = new T[2 * size];
T *ptemp = _allocator.allocate(2 * size);
for (int i = 0; i < size; ++i) {
//ptemp[i] = _first[i];
_allocator.construct(ptemp + i, _first[i]);
}
//delete[] _first;
for (T *p = _first; p != _last; ++p) {
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = ptemp;
_last = _first + size;
_end = _first + 2 * size;
}
};
测试代码如下:
int main () {
CMyString str1 = "aaa";
::vector<CMyString> vec;
cout << "-----------------------" << endl;
vec.push_back(str1); //传了一个左值,引用的是普通的带左值引用的拷贝构造
vec.push_back(CMyString("bbb")); //传了一个右值,引用的是一个带右值引用的拷贝构造
cout << "-----------------------" << endl;
return 0;
}