我有一个简单的 Box 容器,带有一个简单的实现,它需要 Car
#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;
}
Run Code Online (Sandbox Code Playgroud)
o/p
def
def
def
def
assign
assign
def
def
copy
copy
Run Code Online (Sandbox Code Playgroud)
new[]将分配内存与开始数组中元素的生命周期相结合。正如您所见,这可能有问题,因为它调用每个元素的默认构造函数。
什么std::vector是使用std::allocator(或您作为第二个模板参数提供的任何分配器)来分配内存,然后使用放置new来开始一个接一个数组元素的生命周期。Placement 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]);
}
}
Run Code Online (Sandbox Code Playgroud)
使用这种方法,您不能使用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);
}
Run Code Online (Sandbox Code Playgroud)
复制赋值运算符会涉及这两个过程,首先释放清理之前的元素,然后复制新元素。
这是一个非常基本的实现Box:https : //godbolt.org/z/9P3sshEKa
它仍然缺少移动语义,以及任何类型的异常保证。考虑如果new (ptr + i) Car (other.ptr[i]);抛出异常会发生什么。您即将清理所有先前创建的实例以及存储。例如,如果它抛出i == 5您需要调用Car对象 0 到 4的析构函数,则deallocate存储。
总的来说,为你std::vector做了很多繁重的工作。很难正确复制其功能。