zoukankan      html  css  js  c++  java
  • Effective STL~2 vector和string(条款13~18)

    第13条:vector和string优先于动态分配的数组

    使用new来动态分配内存,意味着使用者将承担责任:

    1. 必须确保有人会用delete删除所分配内存。如果没有,new将导致内存泄漏;
    2. 必须确保使用正确delete形式。单个对象删除用delete,数组删除用delete[];
    3. 必须确保只delete一次。如果new一次,delete多次,结果未定义;

    而使用STL顺序容器vector和string,其包含的元素会自动构建、析构,无需额外处理。因此,优先使用vector和string替换动态分配的数组。

    通常,只有一种情况下,用动态分配的数组(new [])取代vector和string是合理的,且只对string适用:在多线程环境中使用了引用计数的string,而避免内存分配和字符拷贝节省的时间还比不上同步控制上的时间。

    如何确认string实现使用了引用计数方式?
    1)查阅库文档;
    2)检查string源码实现。注意string是basic_string的类型定义,wstring是basic_string<wchar_t>的类型定义。

    对于使用引用计数的string在多线程花费较大时间,可以由3种选择:
    1)检查库实现,看是否可能禁用引用计数;
    2)寻找或开发另一个不使用引用计数的string实现;
    3)考虑使用vector而不是string;

    [======]

    第14条:使用reserve来避免不必要的重新分配

    vector和string的内存增长过程,调用类似于realloc的操作:
    1)分配一块大小为当前容量的某个倍数新内存,作为初始内存。大多数实现中,vector、string容量以2倍数增长;
    2)把容器的所有元素从旧内存拷贝到新内存;
    3)析构就内存中的对象;
    4)释放旧内存;

    频繁的扩容,非常耗时,会影响程序效率。如果知道元素的大致数量,从而使用reserve成员函数,强迫容器容量变成指定值,可以有效避免频繁扩容。

    vector<int> vec;
    vec.reserve(1000);
    // 循环过程vec不会发生扩容
    for (size_t i = 0; i < 1000; i++)
    {
           vec.push_back(i);
    }
    

    [======]

    第15条:注意string实现的多样性

    一个string对象大小是多少?即sizeof(string)值是多少?
    有的string实现是4,有的是28。

    cout << sizeof(string) << endl;

    为什么?
    我们先看string实现。几乎每个string都包含如下信息:

    • 字符串大小(size),即所包含的字符个数 ;
    • 用于存该字符串中字符的内存的容量(capacity);
    • 字符串的值(value),即构成该字符串的字符;
    • 它的分配子的一份拷贝(可选,见条款10);

    如果是建立在引用计数基础上的string实现可能还包括:

    • 对值的引用计数;

    4种不同string实现
    1)A实现 sizeof(string) = 28

    默认分配子allocator为指针的4倍空间,自定义分配子可能更大。RefCnt是引用计数。

    2)B实现 sizeof(string) = 4

    “其他”中,包含了一些与多线程环境下同步控制相关的额外数据,实现同步控制的数据大小是指针6倍。RefCnt是引用计数。

    3)C实现 sizeof(string) = 4

    C实现没有对单个对象的分配子支持。X表示不考虑共享问题(一个引用计数值)。

    4)D实现 sizeof(string) = 28

    分两种情况,对应2种数据结构:容量<=15,容量>15。

    对应以下代码:

    string s("Perse");
    

    实现D不会导致任何动态分配,实现A、C将导致一次,B会导致2次。实现B中包含对多线程环境的同步支持。实现C不支持针对单个对象的分配子,意味着只有它可以共享分配子。实现D没有使用引用计数,所有string都不共享数据。

    [======]

    第16条:了解如何把vector和string数据传给旧的API

    对于需要旧式C风格字符串的地方,可以使用c_str成员函数将string传递给const char*

    // C API
    void doSomething(const char* pString);
    
    // 调用
    string s;
    ...
    doSomething(s.c_str());
    

    对于需要旧式C风格T*(数组指针)和size_t(数组大小)的地方,可以使用vector元素的取地址运算符&和vector的size成员函数转换。

    // C API 向pArray[0..arraySize-1]写入, 返回已被写入元素个数
    size_t fillArray(double* pArray, size_t arraySize);
    
    int maxNumDoubles = 10;
    vector<double> vd(maxNumDoubles);
    vd.resize(fillArray(&vd[0], vd.size()));
    

    这项技术只对vector有效,因为只有vector才保住和数组有同样的内存布局。不过,string对象初始化时,可以用vector来进行。

    将除vector和string以为的STL容器把数据传递给C API,可以先把容器元素拷贝到vector,然后再传给C API

    // C API
    void doSomething(const int* pInts, size_t numInts);
    
    set<int> intSet;
    ...
    vector<int> v(intSet.begin(), intSet.end()); // 把set数据拷贝到vector
    if (!v.empty()) doSomething(&v[0], v.size()); // 把数据从vector传给API
    

    [======]

    第17条:使用“swap技巧”除去多余的容量

    shrink to fit(压缩至适当大小)问题
    假设我们向contestants矢量添加一些元素后,又想删除。如果使用条款5介绍的remove+erase成员函数的方法,能从vector删除元素,但无法改变其容量。如果想把它的容量从以前的最大值缩减到当前需要的数量时,该怎么办?这种容量的缩减通常称为“shrink to fit”。

    使用swap实现“shrink to fit”,从contestants矢量中除去多余的容量:

    vector<int> contestants = { 1, 2, 3, 4, 5, 6, 7, 8 };
    // size: 8 capacity: 8
    cout << "size: " << contestants.size() << " capacity: " << contestants.capacity()  << endl;
    // 使用remove + erase成员函数并不能改变vector容量
    contestants.erase(remove(contestants.begin(), contestants.end(), 2));
    
    // size: 7 capacity: 8
    cout << "size: " << contestants.size() << " capacity: " << contestants.capacity()  << endl;
    
    // 使用swap从contestants矢量中除去多余的容量
    vector<int>(contestants).swap(contestants);
    // size: 7 capacity: 7
    cout << "size: " << contestants.size() << " capacity: " << contestants.capacity()  << endl;
    

    vector(contestants)创建一个临时矢量,是contestants的拷贝:由vector的拷贝构造函数来完成。vector拷贝构造函数只为所拷贝的元素分配所需要的内存,因此临时矢量是没有多余容量的。
    然后,用swap操作交换临时矢量和contestants矢量。

    使用swap清除容器

    vector<double> v;
    string s;
    ... // 往v、s中添加、删除数据
    vector<double>().swap(v); // 清除v并把容量变为最小
    string().swap(s); // 清除s并把容量变为最小
    

    注意:string、vector的大小不一定是0,取决于库容器实现。

    [======]

    第18条:避免使用vector

    vector不是一个STL容器,也不存储bool。

    为什么说vector不是一个STL容器?
    因为一个对象要成为STL容器,就必须满足条款23列出的所有条件,其中一个是:如果c是包含对象T的容器,而且支持operator[],那么下面代码必须能被编译:

    T* p = &c[0]; // 用operator[]返回变量的地址初始化一个T*变量
    

    如果你用operator[] 取得了Container中的一个T对象,那么你可以通过它的地址得到一个指向该对象的指针(如上面的&c[0])。
    因此,如果vector是一个容器,那么下面代码可以被编译:

    vector<bool> v;
    bool* pb = &v[0]; // 实际上无法通过编译,因为&v[0]的类型是reference*,而非bool*
    

    但它不能被编译,因为vector并不是一个真的容器,不真正存储bool。为了节省空间,它存储的是bool的紧凑表示:每个bool仅占用1bit空间。而指向单个bit的指针或引用,是被禁止的。

    那如何解决vector::operator[]返回值是T&的问题呢?
    可以使用代理对象(proxy object):

    template<typename Allocator>
    vector<bool, Allocator> {
    public:
        class reference { ... }; // 用来为指向某个bit的引用而产生的代理类
        reference operator[](size_type n); // operator[]返回一个代理
        ...
    };
    

    既然vector应当避免,那么需要vector时,应该使用什么?
    标准库提供了2个选择:
    1)deque
    deque是一个STL容器,而且存储bool。但元素内存不是连续的,因此不能传递给一个期望bool数组的C API。

    2)bitset
    bitset不是标准C++库的一部分。大小(元素个数)编译时确定,因此不支持插入和删除元素。

    标准库为什么会保留vector,而不删除呢?
    因为最初C++标准委员会打算,用vector演示STL如何支持“通过代理来存储其元素的容器”。人们在实现自己的基于代理的容器时,就有现成的参考。不过,要创建一个代理的容器,同时又要满足STL容器的所有要求是不可能的。由于各种原因,他们的尝试被遗留在标准中。

    [======]

  • 相关阅读:
    linux下启动和关闭网卡命令及DHCP上网
    python 编码问题
    paddlepaddle
    Convolutional Neural Network Architectures for Matching Natural Language Sentences
    deep learning RNN
    Learning Structured Representation for Text Classification via Reinforcement Learning 学习笔记
    Python IO密集型任务、计算密集型任务,以及多线程、多进程
    EM 算法最好的解释
    tensorflow 调参过程
    tensorflow 学习纪录(持续更新)
  • 原文地址:https://www.cnblogs.com/fortunely/p/15707313.html
Copyright © 2011-2022 走看看