class Test
{
public:
Test() { cout << "Test()" << endl; }
~Test() { cout << "~Test()" << endl; }
Test(const Test& src) { cout << "Test(const Test& src)" << endl; }
};
int main()
{
Vector<Test> vec; // 开辟堆内存空间,并且通过Test的拷贝构造在堆内存上构造对象
return 0;
}
我们仅仅只是定义了一个vector容器对象,但是由于在构造函数中使用了new ,所以在构造容器对象的时候new既开辟了内存,又构造了对象;
此外,在析构的时候,直接使用delete []_first, 相当于把_first所指向的数组空间中每个元素都当作有效的Test对象进行析构,可实际上数组中很可能只有几个有效元素,所以正确的处理方式应该是:析构容器中的有效元素,然后释放_first指向的堆内存。
我们期望的操作是:
当构造一个容器对象的时候,仅仅是开辟了一块数组内存空间,并不会直接在内存上构造对象;
当我们使用push_back添加元素时,在容器对象的_last处构造对象;
当我们使用pop_back删除末尾元素时,在容器对象的_last-1处析构对象;
当析构容器对象时,先析构容器对象上的有效元素,然后再释放容器内存
从上面的期望可以看出,我们需要将内存的开辟释放以及对象的构造和析构分开处理,于是就有了空间配置器。
容器的空间配置器allocator就是完成做四件事情:
-
内存开辟/内存释放
malloc只开辟内存,不初始化;free只释放内存
-
对象构造/对象析构
调用指定对象的构造函数和析构函数
定义容器的空间配置器
// 定义空间配置器
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);// 在p指向的内存上拷贝构造对象
}
void destroy(T* p) // 析构对象
{
p->~T();
}
};
添加了空间配置器后的顺序容器vector的实现
template<typename T, typename Alloc=Allocator<T>> // 类型参数列表,默认配置器的类型为Allocator<T>
class Vector
{
public:
// 可以通过构造函数传入空间配置器对象,简单起见,这里就不传入了
//Vector(size_t size = 10, const Alloc& alloc){}
Vector(size_t size = 10)
{
_first = _alloc.allocate(size);// 开辟一块数组内存空间
_last = _first;
_end = _first + size;
}
~Vector()
{
// 先析构容器中的有效元素
for (T* p = _first; p != _last; ++p)
{
_alloc.destroy(p);
}
// 再释放整个容器内存
_alloc.deallocate(_first);
// 避免野指针
_first = _last = _end = nullptr;
}
// 因为成员变量会访问外部资源,所以拷贝构造和赋值重载需要进行深拷贝
Vector(const Vector<T>& rhs)
{
int size = rhs._end - rhs._first;
int len = rhs._last - rhs._first;
_first = _alloc.allocate(size); // 开辟内存
for (int i = 0; i < len; ++i)
{
_alloc.construct(_first + i, rhs._first[i]);
}
_last = _first+len;
_end = _first + size;
}
// 返回引用类型,便于连续赋值
Vector<T>& operator=(const Vector<T>& rhs)
{
// 防止自赋值
if (this == &rhs)
return *this;
// 先析构原容器的有效元素,并释放原容器内存
for (T* p = _first; p != _last; ++p)
{
_alloc.destroy(p);
}
_alloc.deallocate(_first);
int size = rhs._end - rhs._first;
int len = rhs._last - rhs._first;
_first = _alloc.allocate(size);
for (int i = 0; i < len; ++i)
{
_alloc.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
return *this;
}
// 从容器末尾添加元素(在_last指针指向的内存构造一个对象)
void push_back(const T& val)
{
if (full())
expand();
//_last先使用后++,在_last所指向的内存上,通过val拷贝构造新对象
_alloc.construct(_last++, val);
}
// 从容器末尾删除元素
void pop_back()
{
if (empty())
throw "vector is empty!";
--_last;// _last指向有效元素的后继位置,所以先--
_alloc.destroy(_last);// 析构末尾有效元素
}
// 获取容器末尾元素
T back()const
{
return *(_last - 1);
}
// 判断容器是否已满
bool full()const
{
return _last == _end;
}
// 判断容器是否为空
bool empty()const
{
return _first == _last;
}
private:
T* _first; // 指向数组起始位置
T* _last; // 指向最后一个有效元素的后继位置
T* _end; // 指向数组空间的后继位置
Alloc _alloc; // 空间配置器对象
void expand()
{
size_t size = _end - _first;
T* ptmp = _alloc.allocate(2 * size);
for (size_t i = 0; i < size; ++i)
{
_alloc.construct(ptmp + i, _first[i]);
}
// 析构原来的有效元素
for (T* p = _first; p != _last; ++p)
{
_alloc.destroy(p);
}
_alloc.deallocate(_first);
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};
测试
int main()
{
Vector<Test> vec;
Test t1, t2, t3;
vec.push_back(t1);
vec.push_back(t2);
vec.push_back(t3);
cout << "======================" << endl;
vec.pop_back();
cout << "======================" << endl;
return 0;
}
可以看到,加入了空间配置器后,执行的操作就符合我们的期望了,构造容器对象时仅仅是开辟了内存,但是并没有在内存上直接构造Test对象,当执行push_back向容器中添加元素时,才在容器相应位置利用已经构造的对象(t1,t2,t3)拷贝构造新的对象,析构的时候也是先析构容器的有效元素,然后再释放数组内存。