c++ STL 常用容器元素类型相关限制 指针 引用
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的生存周期到了,被释放的时候,同时会导致原有实值被删除!这往往不是我们想要的。
例如:
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做啥都要崩溃了
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容器的概念和”值“的语意。