有必要在重新构造字符串之前先销毁它吗

Is it nessary to destroy a string before constructing it again?

本文关键字:字符串 新构造      更新时间:2023-10-16

作为一个练习,我尝试在不使用模板的情况下编写一个类似std::vector的类。它持有的唯一类型是std::string

以下是strvec.h文件:

class StrVec
{
public:
//! Big 3
StrVec():
element(nullptr), first_free(nullptr), cap(nullptr)
{}
StrVec(const StrVec& s);
StrVec&
operator =(const StrVec& rhs);
~StrVec();
//! public members
void push_back(const std::string &s);
std::size_t size() const        { return first_free - element; }
std::size_t capacity() const    { return cap - element; }
std::string* begin() const      { return element; }
std::string* end() const        { return first_free; }

void reserve(std::size_t n);
void resize(std::size_t n);
//^^^^^^^^^^^^^^^^^^^^^^^^^^^
private:
//! data members
std::string* element;       //  pointer to the first element
std::string* first_free;    //  pointer to the first free element
std::string* cap;           //  pointer to one past the end
std::allocator<std::string> alloc;
//! utilities 
void reallocate();
void chk_n_alloc()      { if (size() == capacity()) reallocate(); }
void free();
void wy_alloc_n_move(std::size_t n);
std::pair<std::string*, std::string*>
alloc_n_copy (std::string* b, std::string* e);
};

三个string*elementfirst_freecap可以被认为是:

[0][1][2][3][unconstructed elements]
^           ^                       ^
element     first_free              cap

在实现成员resize(size_t n)时,我遇到了一个问题。比方说,调用v.resize(3)。因此,指针first_free必须向前移动一个位置并指向[3]。类似于:

[0][1][2][3][unconstructed elements]
^        ^                          ^
element  first_free                 cap

我的问题是我应该如何处理[3]?把它留在那里不动?或者像一样销毁它

if(n < size())
{
for(auto p = element + n; p != first_free; /* empty */)
alloc.destroy(p++);
first_free = element + n;
}

这里需要alloc.destroy( somePointer)代码吗?

是的,当然,当使用小于矢量当前大小的参数调用resize()时,您应该对从矢量中删除的元素调用destroystd::vector也是这样做的。

注意,destroy只调用那些元素上的析构函数;它不会释放任何空间(这是错误的)。

由于您正在处理std::string,您可能认为如果您确信稍后使用新值重新初始化相同的std::string对象,则可以不进行销毁。但首先,您不能确定新字符串稍后是否会存储在同一个位置,其次,对于新字符串,将创建一个新对象(使用placement-new,而不是复制赋值),从而泄漏前一个字符串(其析构函数永远不会被调用)的内存。

您应该做什么取决于您初始化element的方式,因为您需要代码保持一致。

  • 如果您使用new std::string[n]创建字符串数组,那么它们都将被预初始化,并且当您稍后必须使用delete[]来释放它们时,它们的析构函数都将运行。出于这个原因,除非您确信将再次在其中放置-new一个有效对象,否则您不能在中间时间手动调用析构函数。

  • 如果您使用类似static_cast<std::string*>(new char[sizeof(std::string) * n])的东西来创建未初始化内存的缓冲区,那么您必须全权负责在适当的时候调用每个元素的构造函数和析构函数

使用第一个选项,您不需要为resize(3)做任何事情,但如果需要,可以在字符串上调用.clear()以释放一些内存。

对于第二个选项,您必须触发[3]的析构函数(除非您保留了最终需要销毁哪个元素的其他记录,这似乎是一个笨拙的模型)。

这些问题与在程序的不同时间"使用"单个字符串的内存相同。你是在第一次使用之前花时间构建它,然后分配给它,还是让它未初始化,然后用放置new复制构建它?你是在未使用时clear还是销毁它?任何一种模型都可以通过仔细的实现来工作。第一种方法往往更容易正确实现,当阵列容量远大于最终使用的元素数量时,第二种模型的效率略高。