c++ STL 常用容器元素类型相关限制 指针 引用

来源:互联网 发布:闻道软件 编辑:程序博客网 时间:2024/05/20 03:38

转自: http://www.cnblogs.com/my_life/articles/4044921.html

c++ 的 STL 中主要有 vector , list, map, set  , multimap,multiset  

 

这些容器完全支持使用内置类型和指针(指针注意内存泄露问题)。

就是说乱用智能指针或其他指针作为容器元素,有可能2个元素指向同一个对象,2个元素(指针)对应一个对象,甚至更多

C++ 容器要求元素具有 object type,引用不是 object type。

 

复制代码
#include <vector>#include <boost/shared_ptr.hpp>using namespace std;class test {};typedef boost::shared_ptr<test> test_ptr;int main(){    vector<test> tmp;    vector<test*> tmp1;    //小心内存泄露,重复析构等问题    //vector<test&> tmp;    //直接编译通不过    vector<test_ptr> tmp2;    //vector<test_ptr&> tmp3;  //即使是boost的智能指针的引用也不行    return 0;}
复制代码

 

 

 

 

 

这些容器都要求元素类型满足以下2种情况:

(1)能被复制:向这些容器添加新元素时,容器会复制一份自己的版本,这要求容器使用的元素类型可以被复制,类类型需要复制构造函数的支持了;

(2)能被赋值:在使用容器的删除、查找、访问、使用迭代器修改元素等许多情况下,都需要元素的赋值操作支持,类类型需要赋值操作符运算的支持。

 

vector、list 中的单参数的resize 操作需要默认初始化指定个数的元素,类类型需要无参数的默认构造函数支持初始化。

 

set、multiset, map和multimap中的键类型、  list 中的sort 操作 都需要 < 比较操作来排序,类类型需要 < 操作符运算的支持。

 

在STL中,容器的元素要满足三个基本要求:可拷贝(copyable)、可赋值(assignable)、可析构(destroyable)。基本数据类型和自定义的类都满足这些条件,但是引用不满足,因为引用不能析构。

 

===========================

http://wenku.baidu.com/view/8d049f4f767f5acfa1c7cd11.html

【摘要】对C++语言本身来说,它并不在乎用户把什么类型的对象作为STL容器的元素,因为模板类型参数在理论上可以为任何类型。比如说STL容器仅支持“值”语义而不支持“引用(&)”语义并非因为模板类型参数不能为引用,而是因为如果容器元素为引用类型,就会出现“引用的引用”、“引用的指针”等C++语言不支持的语法和语义。智能指针是一种模拟原始指针行为的对象,因此理论上也可以作为容器的元素,就象原始指针可以作为容器元素一样。但是智能指针毕竟是一种特殊的对象,它们在原始指针共享实值对象的基础能力上增加了自动销毁实值对象的能力,如果将它作为容器的元素,可能导致容器之间共享元素对象实值,这不仅不符合STL容器的概念和“值”语义,也会存在安全隐患,同时也会存在许多应用上的限制,特别是象STL中的auto_ptr这样的智能指针。

 

可以作为STL容器的元素的数据类型一般来说需要满足下列条件: 
(1)可默认构造的(Default Constructible),也即具有public的default constructor,不论是用户显式定义的还是编译器自动合成的。但是用户定义的带参数的constructor(包括copy constructor)会抑制编译器合成default constructor。实际上并非任何情况下任何一种容器都强制要求其元素类型满足这一要求,特别是关联式容器,因为只有序列式容器的某些成员函数才可能明确地或隐含地使用元素类型的默认构造函数,如果你不使用这样的成员函数,编译器就不需要元素类型的默认构造函数; 
(2)可拷贝构造(Copy Constructible)和拷贝赋值(Copy Assignable)的,即具有public的copy constructor和copy assignment operator,不论是编译器自动合成的还是用户显式定义的。其它版本的operator=()重载并不会抑制编译器合成copy assignment operator,如果你没有显式定义它的话。这个条件可归结为:元素必须是可拷贝的(Copyable),但实际上拷贝赋值的要求也不是强制的,原因和默认构造函数类似; 

(3)具有public的destructor,不论是编译器自动合成的还是用户显式定义的; (4)对于关联式容器,要求其元素必须是可比的(Comparable)。 
std::auto_ptr满足上述条件吗?至少满足前三条,因此至少可以作为序列式容器的元素;如果为auto_ptr定义了比较运算符的话,应该还可以把它作为关联式容器的元素。 
但是auto_ptr的特点是接管和转移拥有权,而不是像原始指针那样可以共享实值对象,

int tmp = 10;

int* p1 = &tmp;

int* p2 = &tmp;

指针p1 p2共享实值对象tmp;

即:auto_ptr在初始化时接管实值对象和拥有权,而在拷贝时(拷贝构造和拷贝赋值)会交出实值对象及其拥有权。因此,auto_ptr对象和它的拷贝绝对不会共享实值对象,任何两个auto_ptr也不应该共享同一个实值对象。这就是说,auto_ptr对象和它的拷贝并不相同。然而根据STL容器“值” 语义的要求,可拷贝构造意味着一个对象必须和它的拷贝相同(标准中的正式定义比这稍复杂一些)。同样,可赋值意味着把一个对象赋值给另一个同类型对象将产生两个相同的对象。显然,auto_ptr不能满足这一要求,似乎与上面的结论矛盾!

 

STL容器管理元素的方法是动态创建元素的拷贝

 

应该说,从应用的方便性和安全角度出发,容器应该要求其元素对象的拷贝与原对象相同或者等价,但auto_ptr显然不满足这一条。

 

==================

http://bbs.csdn.net/topics/310036165

容器元素比如vector对元素对象的唯一要求是可以复制构造。
但比如说你把auto_ptr对象用作了容器元素,虽然其也可以复制构造,只不过复制构造会破坏原始对象,你用了之后会导致未定义现象。

 

容器的大小是可以改变的,而且往往会自动改变。
对于vector来说,如果空间不够了,会自动增长,但是如果原来所在的空间不够的话,系统就会在另外一个地方分配一个满足需要的空间。
所以在此时,对于vector已有的元素也进行了移动,此时就会执行新的构造函数,同样还会把原来位置的旧元素析构掉。

如果同时存在两个vector,其中一个对旧元素执行了析构,会导致另外一个对同一个元素析构,这样就会出问题。

对于你举的例子没有这样的问题,这是因为对于char*执行的值拷贝。

 

所谓的“值语义”就是说可不可以拷贝的问题。std::auto_ptr不满足这个条件。

"值"的语义就是  每个元素都应该是单独的完整的元素,而其元素指针共享同一对象,导致操作一个元素而影响其他元素影响整个容器。。。

 

 

std::auto_ptr这种智能指针的特性,决定了它不适合作为容器的元素的。比如用于vector时,使用push_back(),调用的是复制构造函数。
但auto_ptr在拷贝构造的同时,把原有对象的实值拥有权转给了vector,同时删除了原有的auto_ptr。可能会导致后面使用中的错误。
当使用vector.clear()或者这个vector的生存周期到了,被释放的时候,同时会导致原有实值被删除!这往往不是我们想要的。

 

例如:

1
2
3
4
5
6
typedef auto_ptr<class T> aptr;
aptr p(new T);
vector<aptr> vec;
vec.push_back(p);//此时p被删除,vec.at(0)拥有了原实值
vec.clear();//原实值彻底被删除
p->operation();//还想用p做啥都要崩溃了




1
2
3
4
5
6
int a=5; 
int *p=&a; 
int *q=&a; 
vector <int*> vec1,vec2; 
vec1.push_back(p); 
vec2.push_back(p); 


vec1和vec2中都有p,也就是a的地址,但vector并没有获得a的实值的拥有权!
这里vec1和vec2消逝或者是clear都不会导致a的消亡。

 

容器在存入数据的时候,是存入数据值的一个拷贝,而不是存入的数据的地址。比如说对象a,
存入容器,容器有一个a的拷贝_a,那么_a和a是互相独立的。对容器内_a的操作不会影响a,以上就是
STL容器的概念和”值“的语意。

 

 

条款8:永不建立auto_ptr的容器
坦白地说,本条款不需要出现在《Effective STL》里。auto_ptr的容器(COAPs)是禁止的。试图使用它们的代码都不能编译。C++ 标准委员会花费了无数努力来安排这种情况[1]。我本来不需要说有关COAPs的任何东西,因为你的编译器对这样的容器应该有很多抱怨,而且所有那些都是不能编译的。

唉,很多程序员使用STL平台不会拒绝COAPs。更糟的是,很多程序员妄想地把COAPs看作简单、直接、高效地解决经常伴随指针容器(参见条款7和33)资源泄漏的方案。结果,很多程序员被引诱去使用COAPs,即使建立它们不应该成功。

我会马上解释COAPs的幽灵有多令人担心,以至于标准化委员会采取特殊措施来保证它们不合法。现在,我要专注于一个不需要auto_ptr甚至容器知识的缺点:COAPs不可移植。它们能是怎么样的?C++标准禁止他们,比较好的STL平台已经实现了。可以有足够理由推断随着时间的推移,目前不能实现标准的这个方面的STL平台将变得更适应,并且当那发生时,使用COAPs的代码将更比现在更不可移植。如果你重视移植性(并且你应该是),你将仅仅因为它们的移植测试失败而拒绝COAPs。

但可能你没有移植性思想。如果是这样,请允许我提醒你拷贝auto_ptr的独特——有的人说是奇异——的定义。

当你拷贝一个auto_ptr时,auto_ptr所指向对象的所有权被转移到拷贝的auto_ptr,而被拷贝的auto_ptr被设为NULL。你正确地说一遍:拷贝一个auto_ptr将改变它的值:

1
2
3
4
5
6
auto_ptr<Widget> pw1(new Widget);        // pw1指向一个Widget 
auto_ptr<Widget> pw2(pw1);            // pw2指向pw1的Widget; 
                    // pw1被设为NULL。(Widget的
                    // 所有权从pw1转移到pw2。)
pw1 = pw2;                // pw1现在再次指向Widget;
                    // pw2被设为NULL



这非常不寻常,也许它很有趣,但你(作为STL的用户)关心的原因是它导致一些非常令人惊讶的行为。例如,考虑这段看起来很正确的代码,它建立一个auto_ptr<Widget>的vector,然后使用一个比较指向的Widget的值的函数对它进行排序。

1
2
3
4
5
6
7
8
9
10
bool widgetAPCompare(const auto_ptr<Widget>& lhs, 
            const auto_ptr<Widget>& rhs) {
    return *lhs < *rhs;        // 对于这个例子,假设Widget
}                    // 存在operator<
 
vector<auto_ptr<Widget> > widgets;        // 建立一个vector,然后
                    // 用Widget的auto_ptr填充它;
                    // 记住这将不能编译!
sort(widgets.begin(), widgets.end(),        // 排序这个vector 
            widgetAPCompare);



这里的所有东西看起来都很合理,而且从概念上看所有东西也都很合理,但结果却完全不合理。例如,在排序过程中widgets中的一个或多个auto_ptr可能已经被设为NULL。排序这个vector的行为可能已经改变了它的内容!值得去了解这是怎么发生的。

它会这样是因为实现sort的方法——一个常见的方法,正如它呈现的——是使用了快速排序算法的某种变体。我们不关心快速排序的妙处,但排序一个容器的基本思想是,选择容器的某个元素作为“主元”,然后对大于和小于或等于主元的值进行递归排序。在sort内部,这样的方法多多少少看起来像这样:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<class RandomAccessIterator,        // 这个sort的声明
        class Compare>        // 直接来自于标准
void sort(RandomAccessIterator first,
        RandomAccessIterator last, 
        Compare comp) 
    // 这个typedef在下面解释
    typedef typename iterator_traits<RandomAccessIterator>::value_type 
        ElementType; 
    RandomAccessIterator i; 
    ...                // 让i指向主元
    ElementType pivotValue(*i);        // 把主元拷贝到一个
                    // 局部临时变量中;参见
                    // 下面的讨论
    ...                // 做剩下的排序工作
}



除非你是在阅读STL源代码方面很有经验,否则这看起来可能有些麻烦,但其实并不坏。唯一的难点是引用了iterator_traits<RandomAccessIterator>::value_type,而只不过是传给sort的迭代器所指向的对象类型的怪异的STL方式。(当我们涉及iterator_traits<RandomAccessIterator>::value_type时,我们必须在它前面写上typename,因为它是一个依赖于模板参数类型的名字,在这里是RandomAccessIterator。更多关于typename用法的信息,翻到第7页。)

上面代码中棘手的是这一行,

1
ElementType pivotValue(*i);



因为它把一个元素从保存的区间拷贝到局部临时对象中。在我们的例子里,这个元素是一个auto_ptr<Widget>,所以这个拷贝操作默默地把被拷贝的auto_ptr——vector中的那个——设为NULL。另外,当pivotValue出了生存期,它会自动删除指向的Widget。这时sort调用返回了,vector的内容已经改变了,而且至少一个Widget已经被删除了。也可能有几个vector元素已经被设为NULL,而且几个widget已经被删除,因为快速排序是一种递归算法,递归的每一层都会拷贝一个主元。

这落入了一个很讨厌的陷阱,这也是为什么标准委员会那么努力地确保你不会掉进去。通过永不建立auto_ptr的容器来尊重它对你的利益的工作,即使你的STL平台允许那么做。

0 0
原创粉丝点击