为了练习,我正在尝试实现自己的std :: vector。当前源代码:http://pastebin.com/bE4kjzcb
以下是我班级的概要:
Array()
使用malloc()
push_back(const T &t)
添加一个元素,必要时调用realloc()
。~Array()
致电free()
以释放记忆。这个模型的主要问题是,free()
回收内存,但它不会调用T
的析构函数(当T
是类而不是标准数据类型时) 。
当向量内的元素是对象时,这可能导致严重的资源泄漏。我对此问题的解决方案是,在~T()
内存之前调用free()
显式。
我使用malloc()
的原因是,我正在尝试使用realloc()
。如果我使用new
和delete
,则重新分配时内存使用率将达到峰值。 (新缓冲区和旧缓冲区都存在的时刻。)
问:
这是一个糟糕的设计吗? std::vector
如何解决这个问题?我的矢量类中还有其他缺陷吗?
PS:我们现在不讨论malloc()
的多线程表现。
答案 0 :(得分:5)
调用~T()
完全 std::vector
处理问题的方式。
但是你确实遇到了一些问题:
首先,push_back
需要使用placement new来将值复制 - 构造到向量中。你不能只使用作业。
其次,你不能调用realloc
- 如果对象有内部指针,它们最终会指向自己的外部。您必须再次调用malloc
,然后使用placement new复制构造值,然后明确删除所有旧值,然后调用free
以释放旧值。
(实际上,std::vector
并不会调用~T()
本身。而是调用负责... 分配和释放内存的分配器。 ,这就是通用分配器如何做到的。)
答案 1 :(得分:2)
push_back(const T& t)添加一个元素,必要时调用realloc()。
只要trivially copiable
为realloc
就可以了,例如,尝试推回双链表,并且在重新完成后再取一个并向后迭代 - 应用程序可能会崩溃。解决方案是将函数重载两次,一个用于可琐事可复制的类型,另一个用于非琐事的对象。
与其他人相反,我很遗憾标准容器不会将realloc
用于可以轻易处理的对象。至少在Windows上,size
首先检查当前块是否可以保持新大小,如果是 - 它只是更新堆条目,从而导致巨大的性能提升(无需复制)。
在释放()内存之前显式调用~T()。
是的,这就是标准分配器的工作方式。假设for (auto i=0U;i<size;i++){
data[i].~T();
}
是对象计数,您迭代每一个并手动销毁它:
std::destruct
有趣的是,C ++ 17将添加new[]
,这正是如此。
<强> 加成: 强>
使用delete[]
和new[]
在这里无济于事。通常,动态数组比实现容量所需的空间节省更多空间,额外的空间不是用活动对象填充,只是垃圾。
push_back
将用对象填充整个内存。容量无法以这种方式实现。每当有人推回新元素时,数组将移动/复制整个对象。因此,在1000 O(log (n))
之后,将有1000次重新分配。我们希望摊销时间为new(size_t)
。
即使标准分配器也会调用malloc
或new[]
而不是{{1}}
答案 2 :(得分:0)
而不是致电malloc
和free
,而是使用new
和delete
。调用delete
将确保调用实例dtor。 =)
答案 3 :(得分:0)
如果默认构造函数是为刚刚分配的对象保留的,并且/ move / copy构造函数/赋值运算符和,那么可以使用 new [] T的析构函数传播刚刚分配的或用户对象。 std :: vector中的解决方案及其默认分配器是一个更好的设计。
施工
buffer = new T[capacity];
而不是
buffer = (T*)malloc(capacity * sizeof(T));
和
delete [] buffer;
而不是
free(buffer);
将自动调用每个对象的析构函数,如示例
class A {
public:
~A() { std::cout << "ok" << std::endl; }
};
int main() {
A* a = new A[3];
delete [] a;
return 0;
}
此代码输出3&#34; ok&#34;。然后A应包含其他字段和非默认构造函数,以区分分配和用户构造。
答案 4 :(得分:-1)
这里有一个例子,它是如何工作的或多或少std :: vector:
#ifndef __STDVECTOR__
#define __STDVECTOR__
#include <iostream>
using namespace std;
template <typename T>
class StdVector{
private:
T *buffer;
unsigned int capacity;
public:
//Constructor.
StdVector(){
capacity=0;
buffer=new T[capacity];
}
//Copy constructor.
StdVector(const StdVector &asv){
int i;
capacity=asv.getCapacity();
buffer=new T[asv.getCapacity()];
for (i=0; i<capacity; i++){
buffer[i]=asv[i];
}
}
//Destructor.
~StdVector(){
delete []buffer;
}
void push_back(T obj){
StdVector oldSV(*this);
int i;
capacity++;
delete []buffer;
buffer=new T[capacity];
for (i=0; i<oldSV.getCapacity(); i++){
buffer[i]=oldSV[i];
}
buffer[i]=obj;
};
T getBuffer() const{
if (capacity==0){
throw exception();
}
return *buffer;
};
T &operator[](int index) const{
if (index>=capacity){
//Out of range.
throw exception();
}
else{
return buffer[index];
}
}
StdVector &operator=(const StdVector &obj){
capacity=obj.getCapacity();
delete []buffer;
buffer=new T[capacity];
buffer=obj.getBuffer();
return *this;
}
unsigned int getCapacity() const{
return capacity;
};
};
#endif
int main(){
try{
StdVector<int> test;
StdVector<string> test2;
unsigned int i;
test.push_back(5);
test.push_back(4);
test.push_back(3);
test.push_back(2);
test.push_back(1);
test.push_back(0);
test.push_back(-1);
test.push_back(-2);
test.push_back(-3);
test.push_back(-4);
test.push_back(-5);
for (i=0; i<test.getCapacity(); i++){
cout << test[i] << endl;
}
test2.push_back("Hello");
test2.push_back(" ");
test2.push_back("World");
test2.push_back(".");
cout << "---------------" << endl;
for (i=0; i<test2.getCapacity(); i++){
cout << test2[i];
}
cout << endl;
}
catch(...){
cout << "Exception." << endl;
}
return 0;
}
它打印:
五
4
3
2
1
0
-1
-2
-3
-4
-5
---------------
你好,世界。
也许我有一些错误。如果你知道的话,请跟我说。