vector的扩容机制

vector的扩容机制

我们都知道STL中的vector代表的就是一个可以动态扩容的数组,动态扩容也使它非常好用。但是使用之前,我们需要搞清楚它的底层原理,只有在知道它是如何扩容的,我们才能更安心的使用vector来存储我们的自定义类型。

这篇文章将会解答一下三个问题:

  • 它怎么实现动态扩容的?
  • 它扩容时候调用的是拷贝构造还是移动构造函数?
  • 怎么让它调用我们的移动构造函数?

何时扩容

首先需要对vector的底层结构有一个初步的了解,知道vector如何实现之后,我们甚至可以自己写一个vector容器出来。

  • vector类本身起始只有三个指针,_start、_finish、_end_of_storage,真正存储数据的是一个数组,这三个指针就分别指向这个数组的数据开始的地方,数据结束的地方,和数组的结尾,也就是我们平常使用的begin()、end()、capacity()会用的东西。
  • 然后是vector的迭代器,它的迭代器是所有容器里面最简单,就是原始指针,而其他容器的迭代器因为要实现更多复杂的功能,所以设计成了一个类,比如deque的迭代器就很复杂,为了模拟连续存储的数组。
  • 那回到问题,什么时候扩容?当然就是当_start指针和_end_of_storage指向了同一个地方,说明数组的空间已经用完了,这个时候就需要扩容了。

如何扩容

  • 当数组空间用完了之后,就需要扩容了,但是数组本身无法扩容的,所以vector会申请一块更大的新的内存空间,作为新的数组存储数据。

  • 然后再把旧的数组中的数据一个个拷贝过去,再把原来空间释放掉,就完成了一次扩容。

  • 具体扩容的倍数,在VS中是两倍,不同编译器不同。

int main() {
    vector<int> v;
    v.push_back(1);
    cout << v.capacity() << endl;

    v.push_back(1);
    cout << v.capacity() << endl;

    v.push_back(1);
    cout << v.capacity() << endl;

    v.push_back(1);
    v.push_back(1);
    cout << v.capacity() << endl;
}

输出结果:

1
2
4
8

深浅拷贝

  • 既然发生了拷贝,我们就需要考虑深拷贝与浅拷贝的问题了。
#include <iostream>
#include <vector>

using namespace std;

class A {
public:
    int* data;
    A(int x) : data(new int(x)) {}
    A(const A& x) : data(x.data) {}
    ~A() {
        delete data;
    }
};

int main() {
    A a_1(10);
    A a_2(20);

    vector<A> v{ a_1 };
    v.push_back(a_2);
}
  • 当我们这样子使用的时候,很正常的设计,在构造函数中new,在析构函数中delete,结果最后却因为vector的拷贝让指针重复delete崩溃了,所以我们就设计了深拷贝。

移动构造

  • 但是每次扩容都深拷贝的话,性能消耗太大了,就思考vector能不能调用我们设计好的移动构造
#include <iostream>
#include <vector>

using namespace std;

class A {
public:
    A() {}
    A(const A& other) {
        cout << "拷贝" << endl;
    }

    A(const A&& other) {
        cout << "移动" << endl;
    }
};

int main() {
    //A a;
    vector<A> v;
    v.push_back(A());
    v.push_back(A());
    v.push_back(A());
    v.push_back(A());
}

输出结果:

移动
移动
拷贝
移动
拷贝
拷贝
移动
  • 很遗憾,在我们插入的时候确实调用了移动构造(因为我们插入的是一个临时对象右值),但是扩容的时候仍是拷贝构造。
  • 但是我们知道,vector中可以存放unique_ptr(但是不能直接把一个独占指针直接存入,只能存入右值,因为插入的时候是拷贝的过程),但是unique_ptr的拷贝构造和拷贝赋值都已经delete了,所以在扩容的时候肯定调用的是移动构造函数。
vector<unique_ptr<int>> v;
v.push_back(unique_ptr<int>(new int(10)));
cout << v[0].get() << endl;
cout << v.capacity() << endl;

v.push_back(unique_ptr<int>(new int(10)));
cout << v[0].get() << endl;
cout << v.capacity() << endl;

输出结果:

0x711940
1
0x711940
2
  • 所以我们将拷贝构造设置为delete就能使vector,调用我们的移动构造函数了。
#include <iostream>
#include <vector>

using namespace std;

class A {
public:
    A() {}
    A(const A& other) = delete;

    A(const A&& other) {
        cout << "移动" << endl;
    }
};

int main() {
    //A a;
    vector<A> v;
    v.push_back(A());
    v.push_back(A());
    v.push_back(A());
    v.push_back(A());
}

输出结果:

移动
移动
移动
移动
移动
移动
移动
  • 但是这样我们就不能使用拷贝构造函数了,这肯定是不合理的,所以这里我们得知道为什么编译器不会调用移动构造函数。
  • 原因在于,扩容的时候,一旦移动构造中发生错误,抛出异常就会使扩容失败,如果我们的移动构造函数没有写上noexceptvector就不敢在扩容的时候调用我们自己写的移动构造函数,所以我们需要给移动构造函数表上noexcept
#include <iostream>
#include <vector>

using namespace std;

class A {
public:
    A() {}
    A(const A& other) {
        cout << "拷贝" << endl;
    }
    A(const A&& other) noexcept {
        cout << "移动" << endl;
    }
};

int main() {
    //A a;
    vector<A> v;
    //v.push_back(a);
    v.push_back(A());
    v.push_back(A());
    v.push_back(A());
    v.push_back(A());
}

输出结果:

移动
移动
移动
移动
移动
移动
移动
  • 这样我们既可以拷贝构造,也能让vector在扩容的时候使用我们的移动构造函数了
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的vector是一种动态数组,它可以根据需要自动扩容。当vector中的元素数量超过当前容量时,vector重新分配一块更大的内存空间,并将原有元素复制到新的内存空间中。vector扩容机制可以通过reserve()和capacity()函数来观察和控制。 以下是一个示例代码,演示了vector扩容机制: ```c++ #include <iostream> #include <vector> int main() { std::vector<int> vec; std::cout << "Initial capacity: " << vec.capacity() << std::endl; // 输出:Initial capacity: 0 for (int i = 0; i < 10; i++) { vec.push_back(i); std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl; } // 输出: // Size: 1, Capacity: 1 // Size: 2, Capacity: 2 // Size: 3, Capacity: 4 // Size: 4, Capacity: 4 // Size: 5, Capacity: 8 // Size: 6, Capacity: 8 // Size: 7, Capacity: 8 // Size: 8, Capacity: 8 // Size: 9, Capacity: 16 // Size: 10, Capacity: 16 vec.reserve(20); std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl; // 输出:Size: 10, Capacity: 20 return 0; } ``` 在上面的代码中,我们首先创建了一个空的vector,并输出了它的初始容量。然后我们通过push_back()函数向vector中添加元素,每次添加一个元素后,都输出当前vector的大小和容量。可以看到,当vector的大小超过当前容量时,vector自动扩容,容量扩大的规则是:如果当前容量不足以容纳新元素,则将容量扩大为原来的两倍。最后,我们使用reserve()函数将vector的容量设置为20,并输出了当前vector的大小和容量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值