vector 指针_如何减少 vector 的内存占用

(给CPP开发者加星标,提升C/C++技能)

来源:邱国禄 https://blog.csdn.net/qiuguolu1108/article/details/107146466

【导读】:接上一篇《C++ vector内存分配策略浅析》,其中定义了用于测试的类A,本文中也有用到,大家可以点击链接跳转至上一篇,了解下类A的定义。本片文章从使用角度讲解如何减少vector的内存占用,内容较多,建议先码后看。

以下是正文


vector之所以会发生大量的拷贝,是因为其内存分配策略造成的。每当vector的内存不够用时,vector都会重新申请两倍的空间,并将之前的元素搬移到新空间。这才是发生拷贝的根源,既然这样,我们能不能预先给vector申请一定的空间,避免因空间不够而发生元素搬移。

1、使用reserve()函数预申请空间

reserve()函数可以给vector预先分配指定大小的空间。

vector va;va.reserve(1024);cout<<"va size     = "<endl;cout<<"va capacity = "<endl<<A a;for(int i=0;i<1024;i++){    va.push_back(a);}A::dis_copy_construct_count();cout<<endl;cout<<"va size     = "<endl;cout<<"va capacity = "<endl;
  • 3d03fa25c2bdbde75949b7fe2143d9dc.png

使用reserve()给va预先分配了1024个空间,所以再往va推入1024个元素的时候,并没有发生多余的拷贝构造。ee9b798c68bc59107e7c027727f5774a.png通过reserve()给vector预分配空间,确实可以减少元素的拷贝构造,但我们在使用vector时,有时很难确定容器元素的个数。

在使用reserve()时需要自己去平衡,如果reserve()过大,会造成空间的浪费,如果过小还是会发生拷贝构造。

现在又带来一个问题,如何将vector过多的没有存放元素的空间还给系统。

2、erase()函数和clear()会减少vector的内存占用吗?

上篇文章中,我们说过vector占用空间的大小,可以通过capacity()函数来查看。

通过一个示例,来验证一下erase()、clear()会减少vector内存占用吗?

vector<int> vi;for(int i=0;i<65;i++){    vi.push_back(i);}cout<<"vi size     = "<endl;cout<<"vi capacity = "<endl<<auto itr = find(vi.begin(),vi.end(),30);/*删除0~29元素*/vi.erase(vi.begin(),itr);cout<<"vi size     = "<endl;cout<<"vi capacity = "<endl<<vi.clear();cout<<"vi size     = "<endl;cout<<"vi capacity = "<endl<<

58a8ea6593e1a587c5e0d2b93e2b1c88.png通过测试我们发现,erase()和clear()只能将vector空间的元素给析构掉,并不能减少vector内存的占用。

8f27f02cfcb68955c3cee6635b9bc148.png

这是侯捷老师《STL源码剖析》对vector的erase()、clear()函数实现介绍。这也进一步证实了erase()、clear()并不能释放vector的空间。

3、data()函数可以返回vector元素存放的位置

#include #include using namespace std;int main(){    vector<int> vi;    for(int i=0;i<10;i++)    {        vi.push_back(i);    }    int * p = vi.data();    for(int i=0;i<10;i++)    {        cout<endl;    }    return 0;}

dd87330c19bcc1490402f03216e7e18c.pngdata()这个函数不用多说了,通过示例就可以看出这个函数好强大,直接杀入了vector的老巢。d6919d9d79ffa6fe141024e8888ffd83.png

4、swap()函数用于交换两个vector

swap()函数可以用于交换两个vector,但是交换了vector的哪些东西?

vector<int> vi0;for(int i=0;i<5;i++){    vi0.push_back(i);}cout<<"&vi0  = "<endl;cout<<"vi0.data() = "<endl<<vector<int> vi1;for(int i=0;i<5;i++){    vi1.push_back(i*100);}cout<<"&vi1  = "<endl;cout<<"vi1.data() = "<endl;cout<<endl<<"====================="<<endl<<endl;vi0.swap(vi1);cout<<"&vi0  = "<endl;cout<<"vi0.data() = "<endl<<cout<<"&vi1  = "<endl;cout<<"vi1.data() = "<endl;

b033cb660cf97a4addc4a0d0428264cc.png从示例中可以看出,vector的data()发生了交换,但vi0和vi1所在的地址并没有发生变化。swap()函数还交换了其他内部成员数据,但我们弄清了我们关心的一点,swap并没有发生空间的大量拷贝,交换的仅仅是两个空间地址。678b6c50a130cbec9721bde060215fc3.png补充一点:swap()函数,不仅仅交换两个容器的内容,同时它们的迭代器、指针和引用也被交换。在swap发生后,原先指向容器中元素的迭代器、指针和引用依然有效,并指向同样的元素----但是,这些元素已经在另一个容器中了。

5、vector的拷贝构造

使用一个vector去构造器另外一个vector,在构造新的vector时,仅会根据vector实际元素个数去构造新的vector。

vector<int> vi0;vi0.reserve(100);//插入5个元素for(int i=0;i<5;i++){    vi0.push_back(i);}cout<<"vi0 size     = "<endl;cout<<"vi0 capacity = "<endl<<vector<int> vi1(vi0);cout<<"vi1 size     = "<endl;cout<<"vi1 capacity = "<endl;

cab351b23e4f022c564be48c706ee433.png虽然vi0的内存空间可以存放100个int,但实际有效元素只有5个int。通过vi0拷贝构造vi1的时候,并不会像vi0那样占用100个int空间,而是根据实际元素的个数申请空间,并不会有多余的空间。

6、使用swap技巧移除多余的容量

铺垫了这么多,回到我们的主题上,如何减少vector的容量?

vector的构造器在构建新的容器时,会自动的去掉多余的空间,我们可以利用这个特性,结合swap函数去掉vector中多余的容量。

6.1 方法一:通过定义一个新的vector
#include #include using namespace std;int main(){    vector<int> vi;    vi.reserve(100);    for(int i=0;i<5;i++)    {        vi.push_back(i);    }    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl<<    vector<int> tmp(vi);    cout<<"tmp size     = "<endl;    cout<<"tmp capacity = "<endl<<    cout<<"=================="<<endl<<endl;    tmp.swap(vi);    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl<<    cout<<"tmp size     = "<endl;    cout<<"tmp capacity = "<endl<<    return 0;}

ee017aef47d9b009de74daa697b955a4.pngvi有多余的容量,通过vi构造一个新的容器tmp,tmp没有多余的容量,通过swap函数将tmp和vi交换,则tmp变成了有多余容量的容器。tmp是一个局部变量,在离开其作用域时,会调用vector的析构器,将其自己释放。3cc4b52a7efb171120f1d94527fbd1c3.png

#include #include using namespace std;int main(){    vector<int> vi;    vi.reserve(100);    for(int i=0;i<5;i++)    {        vi.push_back(i);    }    {        vector<int> tmp(vi);        tmp.swap(vi);    }//tmp在此处就会调用vector的析构器,将其自己销毁。        return 0;}

这样可以更加快速的消除多余容量。之前示例,tmp需要在main函数的最后才被销毁,此处的示例,tmp在swap之后就立即被销毁。

6.2 方法二:使用临时变量

上面的方法确实可以消除vector多余的容量,但不够优雅,略显啰嗦。使用临时变量可以更加简洁。

#include #include using namespace std;int main(){    vector<int> vi;    vi.reserve(100);    for(int i=0;i<5;i++)    {        vi.push_back(i);    }    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl<<    {        vector<int>(vi).swap(vi);    }//临时对象在此处离开其作用域,会被销毁。    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl;    return 0;}

6964ed0dd9d880c951bf704f4e59ebe9.png没有定义多余的变量,一行代码搞定,真的很优雅了。

简单的说一下:vector(vi)定义一个临时对象,这个临时对象通过拷贝构造器构造。临时对象调用swap成员函数将其自己和vi交换。临时对象在离开其作用域时被销毁。

6.3 清空vi

clear()仅会清空容器中的元素,并不能真正的释放vector占用的内存。使用swap()可以释放vector内存。

#include #include using namespace std;int main(){    vector<int> vi;    vi.reserve(100);    for(int i=0;i<5;i++)    {        vi.push_back(i);    }    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl<<    {        vector<int>().swap(vi);    }    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl;    return 0;}

2af3736a4cb13aba9dab40e6f43f4931.pngvector()会产生一个临时对象,这个对象没有名字,其size和capacity皆为零。

6.4 总结:

去除vi多余的容量:vector(vi).swap(vi)

将vi的空间清空:vector().swap(vi)

7、使用C++11中shrink_to_fit()函数去除多余容量

看看官方介绍

a53fd52807ce9a290aef3dc8a43cbe16.png

#include #include int main(){    std::vector<int> v;    std::cout << "Default-constructed capacity is " << v.capacity() << '\n';    v.resize(100);    std::cout << "Capacity of a 100-element vector is " << v.capacity() << '\n';    v.clear();    std::cout << "Capacity after clear() is " << v.capacity() << '\n';    v.shrink_to_fit();    std::cout << "Capacity after shrink_to_fit() is " << v.capacity() << '\n';}

0d529dfa28586de40cd333c7dcacb9df.png官方给的示例,很容易理解。

#include #include using namespace std;int main(){    vector<int> vi;    vi.reserve(100);    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl<<    for(int i=0;i<10;i++)    {        vi.push_back(i);    }    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl<<    vi.shrink_to_fit();    cout<<"vi size     = "<endl;    cout<<"vi capacity = "<endl<<    return 0;}
  • 3f29a19482392539dba7b3caf61623af.png

vi的容量是100,向其推入10个元素,则有90个多余的空间,调用shrink_to_fit()后,其容量变为10,释放了多余的空间。

8、减少vector容量,必要的拷贝依然存在。

不管是swap()函数还是shrink_to_fit()函数,在去除vector多余空间的时候,还是会发生必要的元素拷贝。

8.1 swap方法
vector va;va.reserve(10);A a;for(int i=0;i<3;i++){    va.push_back(a);}cout<<endl<<"======================"<<endl<<endl;vector(va).swap(va);cout<<endl<<"======================"<<endl<<endl;

24e886f190016ed1ec89e9d2b12c1a61.png2cbde842922692b16ea759769da0614a.png这里的拷贝构造主要是在构造临时对象产生的。

8.2 shrink_to_fit方法
vector va;va.reserve(10);A a;for(int i=0;i<3;i++){    va.push_back(a);}cout<<endl<<"======================"<<endl<<endl;va.shrink_to_fit();cout<<endl<<"======================"<<endl<<endl;
  • 6e855a1e18623bb11b177d73095218cc.png

结果都是一样的,发生了拷贝构造。

9、resize()函数

顺便说一下resize()函数,这个函数使用也有一定的迷惑性。现在通过几个例子说明一下其背后都做了哪些事情。

现在vector有两个大小,一个是size(),vector实际元素的个数;另一个是capacity(),vector的容量。

size是小于等于capacity的。

9.1 resize()参数小于size()

将多余的元素析构掉

#include #include using namespace std;class A{public:    A(int data = 100)        :data_(data)    {        cout<<"constructor : "<<this<    }    A(const A& a)    {        data_ = a.data_;        cout<<this<<" : copy constructor form : "<    }    void display()    {        cout<" ";    }    ~A()    {        cout<<"deconstructor : "<<this<    }private:    int data_;};int main(){    vector va;    va.reserve(8);    A a(0),b(1),c(2),d(3);    cout<"========================"<    va.push_back(a);    va.push_back(b);    va.push_back(c);    va.push_back(d);    cout<"========================"<    for(auto & i : va)    {        i.display();    }    cout<    va.resize(2);    for(auto & i : va)    {        i.display();    }    cout<    return 0;}
  • f16c458f0ca1d9e200d88ad19189bb58.png

0d5c4cf5b2cb1cd2c383b15dfe3cfb95.png

9.2 resize()参数大于size(),小于capacity()。
9.2.1 情况一:使用默认构造构造新元素
vector<A> va;va.reserve(8);A a(0),b(1),c(2),d(3);cout<"========================"<va.push_back(a);va.push_back(b);va.push_back(c);va.push_back(d);cout<"========================"<for(auto & i : va){    i.display();}cout<va.resize(6);for(auto & i : va){    i.display();}cout<

238a053774a04a73a71f7747f78114f2.png需要添加两个新的元素,没有指定添加的元素,则调用类A的默认构造生成新元素。

A(int data = 100)  :data_(data){  cout<<"constructor : "<<this<<endl;}

这是类A的默认构造器,使用这个构造器生成的对象,其默认值为100。11801c410541522c4540115fcc3783e8.png

9.2.2 情况二:使用指定的元素构造新元素
vector va;va.reserve(8);A a(0),b(1),c(2),d(3);cout<"========================"<va.push_back(a);va.push_back(b);va.push_back(c);va.push_back(d);cout<"========================"<for(auto & i : va){    i.display();}cout<A aa(99);va.resize(6,aa);for(auto & i : va){    i.display();}cout<

a427132f957852af072cc8c05f26ebc2.png和上面类似,但新元素的生成,调用了拷贝构造器。但这里多发生了一次拷贝构造,根据调用情况,猜测resize()先把元素拷贝到其内部,所有新元素的拷贝都是基于resize()内部的副本。

9.3 resize()参数大于capacity()

重新申请空间,将原数据拷贝过来,在新的位置调用默认构造器生成新的元素。

vector<A> va;va.reserve(8);A a(0),b(1),c(2),d(3);cout<"========================"<va.push_back(a);va.push_back(b);va.push_back(c);va.push_back(d);cout<"========================"<for(auto & i : va){    i.display();}cout<va.resize(10);for(auto & i : va){    i.display();}cout<cout<
  • b31e5bf8239064f326daa29b9cb8a9f9.png

6b9aafed9f1d513092c4f4df13376e33.png

10、总结

本文介绍了如何去除vector多余的容量。shrink_to_fit()是C++11提供的新方法,在C++98中可以使用swap()函数实现去除多余的容量。

参考:

《STL源码剖析》

《Effective STL》

- EOF -

推荐阅读   点击标题可跳转

1、C++语言中std::array的神奇用法总结

2、C/C++ assert()函数用法总结与注意事项

3、C++ 的 6 种内存顺序,你都知道吗?

关于如何减少vector的内存占用,欢迎在评论中和我探讨。觉得文章不错,请点赞和在看支持我继续分享好文。谢谢!

关注『CPP开发者』

看精选C++技术文章 . 加C++开发者专属圈子

↓↓↓

a9f3d6d356a1b0c0251e579c62e723e0.png

点赞和在看就是最大的支持❤️

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值