自定义容器在保留空间时不必要地创建新元素实例



为了我自己的教育,我正在努力学习如何在C++中实现高效的自定义容器。我现在有了一个我的自定义向量类型的基本工作版本。然而,由于某种原因,当必须扩展向量以适应更多元素时(在这种情况下,调用其内部的"reserve"函数),它会创建元素的额外副本。

为了帮助解释我的意思,我在下面展示了一个可重复的最低限度的例子。让CustomVector类的最低版本如下所示:

template<class T>
class CustomVector
{
private:
size_t m_size = 0;
size_t m_capacity = 1;
T *m_data = nullptr;
public:
CustomVector()
{
}
CustomVector(const size_t new_capacity)
{
m_capacity = new_capacity;
m_size = 0;
m_data = new T[m_capacity]();
}
~CustomVector()
{
if (m_data != nullptr)
delete[] m_data;
}
void reserve(size_t new_capacity)
{
if (m_data == nullptr)
{
m_capacity = new_capacity;
m_size = 0;
m_data = new T[m_capacity]();
}
else if (new_capacity > m_capacity)
{
T* new_data = new T[new_capacity]();
memmove(new_data, m_data, (m_size) * sizeof(T));
delete[] m_data;
m_capacity = new_capacity;
m_data = new_data;
}
}
void push_back(const T & value)
{
if (m_data == nullptr)
{
m_capacity = 1;
m_size = 0;
m_data = new T[m_capacity]();
m_data[0] = value;
}
else if (m_size + 1 >= m_capacity)
{
reserve(m_capacity*2);
}
else
{
m_data[m_size-1] = value;
m_size++;
}
}

};

现在,为了便于理解这个问题,我还创建了一个名为Object的类。创建的此类的每个新实例都会自动接收一个唯一的id号:

class Object
{
private:
static int idCounter;
public:
int id;
Object()
{
id = idCounter;
idCounter++;
}
};
int Object::idCounter = 0;

最后,下面是这个例子的main函数的样子:

int main()
{
CustomVector<Object> objects; //comment this line...
//std::vector<Object> objects; //...and uncomment this to try with std::vector
Object x;
printf("%dn", x.id);
objects.push_back(x);
Object y;
printf("%dn", y.id);
objects.push_back(y);
Object z;
printf("%d ", z.id);
system("Pause");
return 0;

}

使用我的CustomVector作为容器的输出是:

0 2 5

而使用std::vector作为容器的输出是:

0 1 2

对我来说,理想的行为正是std::vector的行为,也就是说,推回类的实例应该创建此类的全新临时实例。

有人能帮我理解我做错了什么吗?

问题很可能是push_back函数中的这一行:

m_data = new T[m_capacity]();

这将导致创建m_capacity数量的T对象,因此m_capacity调用T构造函数。如果T构造函数很昂贵(更不用说一些初学者在构造函数中做输入和其他事情),这就很糟糕了。

std::vector最可能做的是保持字节的缓冲区,然后在向后推时,它会放置新的,以在缓冲区的某个位置构建一个对象。

最新更新