关闭

STL中vector值得注意的两个问题

299人阅读 评论(0) 收藏 举报

一、 相关背景:

1.      STL有非常重要的两块内容,一个是容器,另外一个是算法。

2.     容器有顺序容器、关联容器和顺序容器适配器之分。算法独立于容器存在,但可以和容器紧密结合使用,从而发挥出

相当大的威力。

3.         顺序容器包括:vectorlistdeque

关联容器包括:mapmultimapsetmultiset

顺序容器适配器包括:stackqueuepriority_queue

 

尽管从名字上看起来很像容器,但bitset其实并不是容器,它只是一种数据类型。

 

stack可以由vectorlist或者deque作为底层容器。缺省地,stack的底层容器是deque

queue可以由list或者deque作为底层容器;缺省地,queue的底层容器deque

priority_queue可以由vectordeque作为底层容器。缺省地,priority_queue的底层容器vector

 

二、vector对象超出其作用域时的情况

vector支持随机访问,因而要取得其中的某个元素的效率是非常高的。除最后一个元素外,要删除其中一个元素,由于涉及到内存迁移,平均效率相对list而言要低很多。

 

下面要谈到的问题,效率并不在考虑范围之内。

vector::erase的原型如下:

iterator erase(iterator position);

iterator erase(iterator first, iterator last);

对应的相关说明如下:

Parameters:

position

Iterator pointing to a single element to be removed from the vector.

first, last

Iterators specifying a range within the vector to be removed: [first,last). i.e., the range includes all the elements between first and last, including the element pointed by first but not the one pointed by last.

 

Removes from the vector container either a single element (position) or a range of elements ([first,last)).

 

This effectively reduces the vector size by the number of elements removed, calling each element's destructor before.

 

Because vectors keep an array format, erasing on positions other than the vector end also moves all the elements after the segment erased to their new positions, which may not be a method as efficient as erasing in other kinds of sequence containers (deque, list).

 

This invalidates all iterator and references to elements after position or first.

上面的说明中,有下划线的那句的含义是:

这实际上就是减少容器的大小,减少的数目就是被删除元素的数目,在删除元素之前,将会调用被删元素的析构函数

 

下面我们来看看代码,是不是真的如此。除非特别说明,所有结果都是VC2005中的情况。

 

 

 

注:下面的代码都在同一个cpp文件中,并有如下语句:

#include <iostream>

#include <algorithm>

#include <string>

#include <vector>

using namespace std;

 

先定义一个类:

class Student

{

public:

         Student(const string name = "Andrew"const int age = 7) : name(name), age(age)

         {

}

 

         ~Student()

         {

                   cout << name << "\tdeleted." << endl;

         }

 

         const string get_name() const

         {

                   return name;

         }

 

         const int get_age() const

         {

                   return age;

         }

 

private:

         string name;

         int age;

};

这个类很简单,将作为vector中的元素类型。它包含了两个私有成员变量nameage,公有的构造函数、析构函数以及分别读取两个成员变量的成员函数get_nameget_age

 

int main(void)

{

         {

                   vector<Student> svec;

                   Student stu01;

                   Student stu02("Bob", 6);

                   Student stu03("Chris", 5);

 

                   svec.push_back(stu01);

                   svec.push_back(stu02);

                   svec.push_back(stu03);

         } // svec的作用域到此结束

 

         cout << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

 

上面程序运行结果:

Andrew     deleted.

Andrew     deleted.

Bob           deleted.

Andrew     deleted.

Bob           deleted.

Chris         deleted.

Chris         deleted.

Bob           deleted.

Andrew     deleted.

Andrew     deleted.

Bob           deleted.

Chris         deleted.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

svec作用域结束前,每个元素对象的析构函数被调用了几次(STL中所有容器分配内存都是使用allocator的机制,如果在代码中显式调用了析构函数,则本想本身是被销毁了,但该对象所占用的内存,并不会交还给系统。可以参考相关资料进行深入研究。在G++中,上述析构函数被调用的次数要比VC2005中少一些,可见不同的编译器,在这方面的实现细节也略有不同。后面玄机逸士会专门撰文对此进行探讨)

 

不管怎么说,vector中的元素对象的确是被销毁了。

 

如果将main函数改成如下的样子:

int main(void)

{

         {

                   vector<Student*> svec;

                   svec.push_back(new Student);

                   svec.push_back(new Student("Bob", 6));

                   svec.push_back(new Student("Chris", 5));

         } // svec的作用域到此结束

         cout << "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

则输出结果:

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

这次显然没有调用元素对象的析构函数。也就是说这些元素并没有被销毁。在G++中的情况也是如此。

 

这就说明:如果vector中的元素是对象指针,则指针所指向的对象在vector的作用域结束时,是不会被自行销毁的。

 

三、vector中的erase

情形1 如果将前面的main函数改成:

int main(void)

{

         {

                   vector<Student> svec;

                   Student stu01;

                   Student stu02("Bob", 6);

                   Student stu03("Chris", 5);

 

                   svec.push_back(stu01);

                   svec.push_back(stu02);

                   svec.push_back(stu03);

 

                   svec.erase(svec.begin(), svec.end());

                   cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

         } // svec的作用域到此结束

         cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

则输出结果为:

Andrew     deleted.

Andrew     deleted.

Bob           deleted.

Andrew     deleted.

Bob           deleted.

Chris         deleted.

Andrew     deleted.

Bob           deleted.

Chris         deleted.

1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Chris         deleted.

Bob           deleted.

Andrew     deleted.

2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

 

很明显1之上的析构函数调用时由erase语句产生的,12之间的语句是svec作用域结束时产生的。因此,This effectively reduces the vector size by the number of elements removed, calling each element's destructor before.在这里是成立的。

g++中的情况与此类似。

 

情形2:如果将main函数改写成:

int main(void)

{

         {

                   vector<Student*> svec;

                   svec.push_back(new Student);

                   svec.push_back(new Student("Bob", 6));

                   svec.push_back(new Student("Chris", 5));

                   svec.erase(svec.begin(), svec.end());

                   cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

         }

         cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

则输出结果为:

1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

 

可见析构函数并没有得到调用,由此可知:

如果vector中的元素是对象指针,则指针所指向的对象在调用完erase时,是不会被自行销毁的,即This effectively reduces the vector size by the number of elements removed, calling each element's destructor before.在这种情况下是不成立的。g++中的情况与此类似。

 

情形3:如果将main函数改写成:

int main(void)

{

         {

                   vector<Student> svec;

                   Student stu01;

                   Student stu02("Bob", 6);

                   Student stu03("Chris", 5);

 

                   svec.push_back(stu01);

                   svec.push_back(stu02);

                   svec.push_back(stu03);

 

// (1)

                   //svec.erase(svec.begin(), svec.end());     

 

                  // (2)

                   for(vector<Student>::iterator iter = svec.begin(); iter != svec.end(); ) 

                   {

                            iter = svec.erase(iter);       // earse函数返回的是,指向被删元素后面那个元素的迭代器

                            //iter--;                                   // 因为上面语句删除了第一个元素stu01,现在iter指向了stu01

                   }                                                       // 后面的元素stu02,由于stu01已经不存在,stu02便是第一个

                                                        // 元素了,亦即此时iter == svec.begin()true的,因此其

// 后的iter--;语句是错误的。

 

// (3)

                   //vector<Student>::iterator iter = svec.begin();            

                   //while(iter != svec.end())

                   //{

                   //       iter = svec.erase(iter);

                   //}

 

                   // (4)

//for(vector<Student>::iterator iter = svec.begin(); iter != svec.end(); iter++)

                   //{

                   //       svec.erase(iter);

                   //       iter--;

                   //}

                   cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

         }

         cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

(1)(2)(3)VC2005中均能够正确删除svec中的所有元素对象,(4)将会出现运行时错误,这是因为erase后会导致iter失效。(1)(2)(3)(4)g++环境中,全部正确。不过建议即便在g++环境中,也不要像(4)那样写代码。

 

情形4:如果将main函数写成:

int main(void)

{

         vector<Student*> svec;

         svec.push_back(new Student);

         svec.push_back(new Student("Bob", 6));

         svec.push_back(new Student("Chris", 5));

         svec.erase(svec.begin(), svec.end());

         cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

         for(vector<Student*>::iterator iter = svec.begin(); iter != svec.end(); iter++)

         {

                   cout << (*iter)->get_name() << endl;

         }

         cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

        

         return 0;

}

则输出结果为:

1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

这说明,erase的确将svec中的元素删除了,但元素本身所指向的内存依然存在于内存中,因为未见到相关析构函数被调用。

 

情形5:如果将main函数写成:

int main(void)

{

    vector<Student*> svec;

    svec.push_back(new Student);

    svec.push_back(new Student("Bob", 6));

    svec.push_back(new Student("Chris", 5));

    //svec.erase(svec.begin(), svec.end());

    cout << "1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

    for(vector<Student*>::iterator iter = svec.begin(); iter != svec.end(); )

    {

              Student *temp = *iter;

        iter = svec.erase(iter);

        //iter--;

              delete temp;

    }  

   

    // 看看svec中是否还有元素?

       cout << "If there is any element in svec?" << endl;

       cout << "There is " << svec.size() << " element(s) in svec." << endl;

 

    cout << "2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=" << endl;

   

    return 0;

}

则输出结果为:

1-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Andrew     deleted.

Bob           deleted.

Chris         deleted.

If there is any element in svec?

There is 0 element(s) in svec.

2-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

这说明,erase的确将svec中的元素删除了,且元素本身所指向的内存也被回收。

 

四、 结论

通过上述代码及其运行结果表明:

1.     vector中,如果元素是对象的指针,当该vector超出其作用域或调用erase删除元素时,那么元素本身在该vector

    种会被删除,但对象本身并没有得到销毁。在这种情况下,销毁的工作要由程序员自己来做。

2.     erase删除vector容器中的元素对象时,元素对象的析构函数会被多次调用。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:25096次
    • 积分:328
    • 等级:
    • 排名:千里之外
    • 原创:6篇
    • 转载:29篇
    • 译文:0篇
    • 评论:1条
    文章分类