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

Veg*_*eta 3 c++ c++11

我有一个简单的 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)
  • std::vector 复制并调用复制构造函数,而 Box 不会。std::vector 的复制构造函数是如何实现的?我做错了什么?
  • std::vector 的内存分配是如何处理的,为什么在 std::vector 中只调用了两次默认构造函数,而在 Box 中调用了 4 次?任何解释就足够了

Fra*_*eux 5

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)

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

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

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

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