这可能是一个幼稚的问题,但简单来说,如何实现与 std::vector 相同的复制构造函数?



我有一个简单的Box容器,它有一个朴素的实现,需要汽车

#include <iostream>
#include <vector>
struct Car { 
Car() { puts("def"); }
Car(Car const& other) { puts("copy"); }
Car& operator=(Car const& other) {
puts("assign");
return *this;
}
};

struct Box {
size_t size;
Car* ptr;
Box(size_t size) 
: size(size)
, ptr{new Car[size]} 
{}

Box(Box const& other) 
: size{other.size} 
{
ptr = new Car[size];
for (int i = 0; i < size; i++)
ptr[i] = other.ptr[i]; // hits operator=
}
};
int main() {
Box b(2);
Box b3 = b;    
std::cout << std::endl;
std::vector<Car> v(2);
std::vector<Car> v2 = v;
}

o/p

def
def
def
def
assign
assign
def
def
copy
copy
  • std::vector 复制并调用复制构造函数,而 Box 不。std::vector 的复制构造函数是如何实现的?我做错了什么?
  • 如何处理 std::vector 的内存分配,为什么默认构造函数在 std::vector 中只调用两次,而在 Box 中调用 4 次?任何解释都足够了

new[]将分配内存与启动数组中元素的生存期结合起来。如您所见,这可能会有问题,因为它调用每个元素的默认构造函数。

std::vector所做的是使用std::allocator(或您作为第二个模板参数提供的任何分配器)来分配内存,然后使用放置new逐个启动数组元素的生命周期。放置 new 是一个新表达式,开发人员在其中提供指向应创建对象的位置的指针,而不是要求new分配新存储。

使用此方法,下面是复制构造函数的简化示例:

Box::Box(const Box & other) : size{other.size} 
{
// Create storage for an array of `size` instances of `Car`
ptr = std::allocator<Car>{}.allocate(size);
for(std::size_t i = 0; i < size; ++i)
{
// Create a `Car` at the address `ptr + i`
//  using the constructor argument `other.ptr[i]`
new (ptr + i) Car (other.ptr[i]);
}
}

使用此方法,不能使用delete[]delete来清理Car元素。您需要反向显式执行前面的过程。首先,通过调用每个Car对象的析构函数来显式销毁所有对象,然后使用分配器释放存储。简化析构函数如下所示:

Box::~Box()
{        
for(std::size_t i = 0; i < size; ++i)
{
// Explicitly call the destructor of the `Car`
ptr[i].~Car();
}
// Free the storage that is now unused
std::allocator<Car>().deallocate(ptr, size);
}

复制赋值运算符将涉及这两个过程,首先释放清理以前的元素,然后复制新元素。

这是一个非常基本的Box实现: https://godbolt.org/z/9P3sshEKa

它仍然缺少移动语义和任何类型的异常保证。考虑如果new (ptr + i) Car (other.ptr[i]);引发异常会发生什么情况。您需要清理所有以前创建的实例以及存储。例如,如果它抛出i == 5则需要调用Car对象 0 到 4 的析构函数,然后deallocate存储。

总的来说,std::vector为你做了很多繁重的工作。很难正确复制其功能。

std::vector使用分配器而不是使用new运算符。关键的区别在于new运算符构造数组中的每个元素。但是 vector 会分配原始内存,并且只按需构造元素。您可以通过使用operator new(而不是new运算符)、malloc、某些分配器或其他方式来实现相同的目的。然后,使用 placement-new 调用构造函数。在析构函数中,您必须单独调用所有元素的析构函数,然后才释放内存。看:

">
  • 新操作员"和"新操作员"之间的区别?
  • 马洛克
  • 标准::分配器
  • 运算符 新
  • 新表达式

此外,您的Box类需要一个operator=,因为默认的类做错了什么。还有一个析构函数。它正在泄漏内存。

也许在实现较低层的分配中使用了引用计数机制。

最新更新