Boost使用笔记(Smart_ptr)
概述
Boost库是一个功能强大、构造精巧、跨平台、开源免费的C++程序库,提供了代码编写中所需要的几乎所有常见工具,例如智能指针、bind、正则表达式、xml解析等工具。其代码以泛型编程为基础,且绝大部分代码放在扩展名为hpp的头文件中,以内联的方式引入到目标程序,因此Boost库几乎无需编译即可使用。最新版的C++标准中已经将boost部分模块纳入其中,足见其功能的强大。目前Boost库是除STL库以外最常用的C++代码库之一。
在实际开发中,Boost多用于应用软件和游戏编程,由于代码量相当庞大,且内部各模块互相引用牵连,致使使用Boost中很小的功能,也要将整个Boost库全部安装,应用上相对冗余,不过由于Boost以泛型编程为基础,实际编译到目标程序中的代码量并不大,且多为Inline形式,效率上也同样不差。
Boost是跨平台的,其代码支持Win、Linux、Vxworks等平台,由于精力和时间有限没有对完整的库在Vxworks下进行验证,经过试验的库有3个:
●smart_ptr
●xpressive
●property_tree
三个库在Vxworks6.4及Vxworks6.8上都做过实验,并且在板卡上试验了Boost的兼容性及性能。在实验中smart_ptr库在Vxworks6.4及Vxworks6.8平台下均可编译执行,由于smart_ptr模块相对其他模块较为独立,现已将其从Boost库中全部抽取出来(大概218个文件)上传到Git中,可以在编码中独立使用。
https://https://www.doczj.com/doc/355910949.html,/guolisen/BoostSmartPtr.git 需要注意的是BoostSmartPtr由Boost 1.43.0代码而来,目前只支持shared_ptr。前面介绍过boost库代码互相牵连,即使加入一个weak_ptr也需要再加入关联的好几百个文件,因此为保证精简性没有将其加入。
xpressive 库是一个用来解析正则表达式的库,由于非常“高级”且庞大,在实验中只进行了基本的编译和使用,没有做过多的尝试。xpressive 库接口简易,功能强大,但同样由于内部牵连过多,因此没能将其抽取出来。
property_tree库已经编译通过但使用中出现崩溃的情况,没有深究崩溃的原因(有可能是编译环境的问题,非代码本身问题),XML解析使用tinyXml已经完全满足要求。
智能指针
在我们日常编码中经常使用到new关键字分配内存,被分配的内存需要在适当的时候调用delete关键字释放,否则可能造成内存泄露导致内存分配失败错误。为了避免这样的错误人们发明了智能指针,其设计思想是管理内存生命周期,使那些从堆中分配的内存在不使用时自动被释放,程序员只需要知道在哪里分配内存,而不用担心是否忘记将其释放。简单的智能指针原理请看下面代码:
template
class simple_smart_ptr
{
public:
simple_smart_ptr(T* mem_ptr):mPtr(mem_ptr) {
assert(mPtr);
std::cout <<"Ptr Create!"<< std::endl;
};
~simple_smart_ptr()
{
assert(mPtr);
std::cout <<"Ptr Destory!"<< std::endl;
delete mPtr;
};
T* operator-> () const
{
assert(mPtr);
return mPtr;
}
private:
T* mPtr;
};
class Test
{
public:
void print()
{
std::cout <<"HeiHei!"<< std::endl;
};
};
void TestFun()
{
simple_smart_ptr
t->print();
}
int main()
TestFun();
return 0;
}
输出:
Ptr Create!
HeiHei!
PtrDestory!
上面是一个智能指针的原型代码,simple_smart_ptr在构造函数获取需要管理的堆指针,即new出来的指针地址。当智能指针结束生命期后,析构函数被调用,被管理的内存被自动释放。
智能指针是一种防止内存泄露的有效手段,甚至可以说是大型软件开发的必用工具。目前使用最广泛的智能指针是std::auto_ptr和boost::smart_ptr,std::auto_ptr出自标准库,不支持引用计数,与STL容器不兼容,在使用上有一定局限。boost::smart_ptr是Boost库的一部分,包括scoped_ptr、shared_ptr、weak_ptr等。Boost的智能指针代码非常优秀,且已经收录到C++最新标准之中,可以放心使用,在下面的章节中会逐步为大家介绍。
std::auto_ptr
std::auto_ptr是标准库中提供的一种智能指针,实现了最基本的内存自动管理机制,其使用方法和上一节用到的simple_smart_ptr基本相同。
#include
class Test
{
public:
void print()
{
std::cout <<"HeiHei!"<< std::endl;
};
};
int main()
{
std::auto_ptr
at->print();
return 0;
}
输出:
HeiHei!
例子程序中使用std::auto_ptr管理在堆中分配的Test对象,当main函数返回的时候,at局部变量结束生命期,析构函数被调用,Test对象的内存自动释放。
由于std::auto_ptr没有实现引用计数机制,如果出现两个std::auto_ptr同时引用同一片内存,将会出现毁灭性的结果。因为此时若其中任何一个指针退出生存周期将会释放对应内存区域,与他拥有相同内存指针的另一个std::auto_ptr将变成“野指针”,若此std::auto_ptr 退出生命周期系统将崩溃。
为了解决这样的问题std::auto_ptr引入了一种叫做“拥有权”的概念,每个需要被管理的原始内存指针只对应一个std::auto_ptr,同一时间只有一个std::auto_ptr对此原始指针有“拥有权”。若对此std::auto_ptr执行复制或将其赋值给其他std::auto_ptr,那么原始指针的“拥有权”将转移到被复制的新std::auto_ptr中(即新std::auto_ptr将拥有原始指针,被复制的std::auto_ptr将指向NULL)。看下面例子:
void testFun(std::auto_ptr
{
p->print();
}
int main()
{
/////////////////////////////////////////////////
std::auto_ptr
std::auto_ptr
b->print();
c = b; //使用operator=使拥有权转移,b不再拥有Test的指针且指向NULL
c->print();
b->print(); //这里系统将奔溃
/////////////////////////////////////////////////
std::auto_ptr
d->print();
std::auto_ptr
e->print();
d->print(); //这里系统将奔溃
/////////////////////////////////////////////////
std::auto_ptr
f->print();
testFun(f); //使用拷贝构造函数使拥有权转移,f不再拥有Test的指针且指向NULL
f->print(); //这里系统将奔溃
return 0;
由上面例子可以看到,当std::auto_ptr发生复制,构造,拷贝构造时std::auto_ptr对原始指针的拥有权将转移,自身将指向NULL,此时再引用此std::auto_ptr进行指针操作时系统将会崩溃(此时已经指向NULL)。拥有权的设计避免了std::auto_ptr指向共享区域从而导致二次释放的问题,同时也规避了线程安全问题(无共享区域)。
但std::auto_ptr在使用中还是有许多坑需要注意:
1.std::auto_ptr用作函数的参数或返回值时需要格外小心,当std::auto_ptr做函数的非引用参数时,由于会调用拷贝构造函数,因此会发生拥有权的转移,此时做参数
的std::auto_ptr将指向NULL,若此时再次引用将产生崩溃
voidtestFun(std::auto_ptr
{
p->print();
}
int main()
{
Test* pa = new Test;
std::auto_ptr
at->print();
testFun(at);//拥有权转移
at->print();//拥有权已经转移,再次引用将崩溃
return 0;
}
2.std::auto_ptr本身与STL容器不兼容,因此不能将其放到std::vector、std::list、std::map 中使用。(但是VC6貌似可以编译通过,足见VC6已经不适合现代开发了,继续使
用将造成巨大的移植隐患)
3.std::auto_ptr不能管理数组指针,因为在析构的时候std::auto_ptr使用的是delete 而不是delete []
可以看到,使用std::auto_ptr还是有很多不方便的地方,且存在很多极容易出错的坑,这也是std::auto_ptr没有被大规模应用的原因,在下一节中我们将介绍Boost的智能指针shared_ptr,shared_ptr是一种建立在引用计数框架下的智能指针,且效率及稳定性极高,不存在兼容性的问题,是应用最广泛的智能指针之一。
boost::shared_ptr
boost::shared_ptr智能指针使用引用计数管理原始指针,其管理方式有些类似COM组件,当指针的使用者增加时,引用计数器加一,当指针退出生命周期时,指针引用者减少时,计数器自动减一。当计数器被减为零时boost::shared_ptr自动释放所指向的内存。boost::shared_ptr在使用上几乎与普通指针无任何区别,且自带内存回收机制,程序员只需关心在哪里new对象,而不用关心何时释放内存,最大程度上避免了内存泄露的出现。下
面我们举例说明如何使用boost::shared_ptr:
#include
class Test
{
public:
Test()
{
std::cout <<"Test Create!"<< std::endl;
}
~Test()
{
std::cout <<"Test Destory!"<< std::endl;
}
void print()
{
std::cout <<"HeiHei!"<< std::endl;
};
};
void func(boost::shared_ptr
{
//进入函数时引用计数加一变为2
p->print();
}//函数返回时引用计数再次减一变为1,因此不会释放内存
int main()
{
///////////////////////////////////////////////// //内部类型的例子
///////////////////////////////////////////////// std::cout <<"<<< Example 1 >>>"<< std::endl;
boost::shared_ptr
std::cout <<"pi: "<< *pi << std::endl;
*pi = 200;
std::cout <<"pi: "<< *pi << std::endl << std::endl;
///////////////////////////////////////////////// //对象类型的例子
///////////////////////////////////////////////// std::cout <<"<<< Example 2 >>>"<< std::endl;
{
boost::shared_ptr
{
boost::shared_ptr
std::cout <<"After Create var 'po2' po: "
<< https://www.doczj.com/doc/355910949.html,e_count()
<<" po2: "
<< https://www.doczj.com/doc/355910949.html,e_count()
<< std::endl;
std::cout <<"Use po: ";
po->print();
std::cout <<"Use po2: ";
po2->print();
} //这里退出时,引用计数减一变为1,因此不释放内存
}//退出生存期时引用计数再次减一变为0,释放内存
std::cout << std::endl;
/////////////////////////////////////////////////
//做参数的例子
/////////////////////////////////////////////////
std::cout <<"<<< Example 3 >>>"<< std::endl;
{
boost::shared_ptr
func(pp);
}//退出生存期时引用计数再次减一变为0,释放内存
std::cout << std::endl;
/////////////////////////////////////////////////
//容器例子
/////////////////////////////////////////////////
std::cout <<"<<< Example 4 >>>"<< std::endl;
typedef boost::shared_ptr
typedef std::vector
{
TEST_PTR pv(new Test);
TEST_VEC vec;
vec.push_back(pv); //引用计数加一,变为2
vec[0]->print();
}//退出生存期时pv,vec分别析构,两次减一,使引用计数变为0,释放内存
return 0;
}
输出:
<<< Example 1 >>>
pi: 100
pi: 200
<<< Example 2 >>>
Test Create!
After Create var 'po2' po: 2 po2: 2
Use po: HeiHei!
Use po2: HeiHei!
Test Destory!
<<< Example 3 >>>
Test Create!
HeiHei!
Test Destory!
<<< Example 4>>>
Test Create!
HeiHei!
Test Destory!
如上的例子中Example1说明了如何利用share_ptr管理内部类型(char、int、float…),Example2处理对象类型,Example3将share_ptr用作函数参数,Example4展示shared_ptr 如何运用在容器中。当然share_ptr还可以做成员变量等,原理相同,这里不再赘述。
share_ptr在使用上和普通指针几乎没有区别,因为share_ptr内部引用计数机制记录着所有对原始指针的引用数(即有多少地方在用这个指针),当某个引用的指针不再使用了(退出生命周期),share_ptr会自动将引用计数减一,当所有引用此指针的地方均退出时(引用计数变为0),说明此指针已没有使用的必要,share_ptr将自动将其释放。
share_ptr在使用中每一次的复制传递都会使share_ptr内部的引用计数改变,通俗一点的解释(当然并不准确,只是为了理解)就是当shared_ptr构造函数、拷贝构造、operator=被调用的时候计数器会加一(若有原引用对象,则原引用对象减一),析构函数被调用的时候会减一,总结一下引用计数的变化情况如下:
计数器加一
1.share_ptr对象建立时引用计数自动加一,如例Example1
2.由原share_ptr对象创建新share_ptr对象时引用计数自动加一,如例Example2建立
指针po2
3.做函数参数时引用计数自动加一,如例Example3
4.做容器中的元素时计数自动加一,如例Example4,pv被push到vector后计数器变
为2
计数器减一
1.share_ptr离开作用域,share_ptr析构函数被调用使引用计数减一
2.share_ptr被复制新值时,原引用对象将被减一,如Example2中po2和po同时管理
同一块内存区域,若po2被其他新share_ptr赋值,则po的引用计数被减一boost::shared_ptr
boost::shared_ptr
boost::shared_ptr
po2 = poo;
std::cout<< https://www.doczj.com/doc/355910949.html,e_count() < std::cout< 3.share_ptr做容器的元素,当从容器中将share_ptr删除时引用计数减一 看起来稍有些复杂,大家可以通过use_count()返回引用计数的方法观察share_ptr指针的计数变化情况,在实际使用中几乎不用关心引用计数的情况shared_ptr会保证内存的正确释放。 以上介绍了shared_ptr的基本使用方法,在实际使用中还需要注意两个问题,第一,shared_ptr不能处理数组元素,即new[]出来的数据不能放到shared_ptr管理,原因与std:auto_ptr相同,shared_ptr在析构的时候使用的是delete而不是delete[],如果需要管理数组元素需要使用shared_array。shared_array与shared_ptr在使用上除了管理内容的区别,使用方法基本可以视为相同,因此不再赘述。 另一方面,引用计数型智能指针还有一个需要注意的问题就是循环引用问题,对象互相引用形成类似“死锁”的状态,使得指针引用计数不可能减为0,导致内存不能释放,请看下面例子: #include class CB; class CA; class CA { public: CA() { std::cout <<"CA Create!"<< std::endl; } ~CA() { std::cout <<"CA Destory!"<< std::endl; } void print() { std::cout <<"CA mSP Ref Count: "<< https://www.doczj.com/doc/355910949.html,e_count() << std::endl; } public: boost::shared_ptr class CB { public: CB() { std::cout <<"CB Create!"<< std::endl; } ~CB() { std::cout <<"CB Destory!"<< std::endl; } void print() { std::cout <<"CB mSP Ref Count: "<< https://www.doczj.com/doc/355910949.html,e_count() << std::endl; } public: boost::shared_ptr }; void func() { boost::shared_ptr boost::shared_ptr std::cout <<"a use_count: "<< https://www.doczj.com/doc/355910949.html,e_count() << std::endl; std::cout <<"b use_count: "<< https://www.doczj.com/doc/355910949.html,e_count() << std::endl; a->mB = b; b->mA = a; a->print(); //互相引用后,a和b的引用计数都变为二 b->print(); std::cout <<"> a use_count: "<< https://www.doczj.com/doc/355910949.html,e_count() << std::endl; std::cout <<"> b use_count: "<< https://www.doczj.com/doc/355910949.html,e_count() << std::endl; } int main() { func(); return 0; } 输出: CA Create! CB Create! ause_count: 1 buse_count: 1 CA mSP Ref Count: 2 CB mSP Ref Count: 2 >ause_count: 2 >buse_count: 2 在func()函数中shared_ptr变量a、b互相引用,使得自身的引用计数都变成了二,当函数返回的时候,局部变量a、b被析构,但由于析构后引用计数为1,不会释放内部的CA、CB对象,因此CA、CB对象也不会调用自身的析构函数释放mA、mB,a、b的引用计数不会被减为零,这样就造成了a、b的内存泄露。循环引用问题是引用计数型智能指针的常见问题,解决办法就是使用另一种辅助指针weak_ptr。 boost::weak_ptr weak_ptr是shared_ptr指针的辅助工具,由shared_ptr指针或其他weak_ptr指针构造产生,其本质是一种弱引用指针,即weak_ptr在使用中不会修改对应shared_ptr指针的引用计数值,也没有对“*”和“->”进行重载,weak_ptr接口非常简单,通常会用到如下两个: expired():返回当前引用计数是否为0的Bool值(use_count() == 0),即当前weak_ptr所指向的shared_ptr是否可用。 lock():若weak_ptr所指向的shared_ptr指针可用则将其返回,否则返回一个指向NULL的shared_ptr。(expired()? shared_ptr 使用weak_ptr解决循环引用问题,只需将上例中任意一个类的成员改成weak_ptr即可,例如做如下修改: class CB { public: CB() { std::cout <<"CB Create!"<< std::endl; } ~CB() { std::cout <<"CB Destory!"<< std::endl; } void print() { std::cout <<"CB mSP Ref Count: "<< https://www.doczj.com/doc/355910949.html,e_count() << std::endl; } public: boost::weak_ptr 只需将mA成员的类型由原来的shared_ptr 输出: CA Create! CB Create! ause_count: 1 buse_count: 1 CA mSP Ref Count: 2 CB mSP Ref Count: 1//这里引用计数已经不是2了,所以不会造成循环引用。 >ause_count: 1 >buse_count: 2 CA Destory! CB Destory! 去除了循环引用,CA、CB对象都得到了释放,但需要注意此时使用mA成员的时候需要调用weak_ptr 以上是使用weak_ptr解决循环引用问题,weak_ptr的另一个作用是保存对象的this指针。某个被shared_ptr管理的类,在某些方法里可能会有类似return this的操作,如下面的GetObj()方法: class TestA { public: TestA() { cout <<"TestA::TestA()"<< endl; } ~TestA() { cout <<"TestA::~TestA()"<< endl; } TestA* GetObj() { returnthis; } }; 很明显,直接返回this指针会使得此对象失控(可能在任何地方被delete),但如果让GetObj()返回一个sherad_ptr class TestA { public: TestA() { std::cout <<"TestA::TestA()"< } ~TestA() { std::cout <<"TestA::~TestA()"< } shared_ptr std::cout <<"TestA::GetObj()"<< std::endl; return shared_ptr } }; 看似this指针在shared_ptr指针的管辖下是安全的,实则并非如此,因为TestA在实际使用中可能是如下形式: void func() { boost::shared_ptr boost::shared_ptr } 也就是说this指针被两个引用计数为一的shared_ptr对象管理,当func函数返回的时候就会造成二次释放,通俗的讲就是要崩溃。 输出: TestA::TestA() TestA::GetObj() TestA::~TestA() TestA::~TestA()//第二次释放崩溃了 如果解决上面问题比较理想的办法是在shared_ptr构造的时候,将其保存在被管理对象中的一个weak_ptr成员变量中,这样在需要的时候只要通过weak_ptr返回this的share_ptr 即可。 根据如上解决思路boost提供了一个叫做enable_shared_from_this工具类,enable_shared_from_this中含有一个保存对象this指针的weak_ptr成员,weak_ptr成员在shared_ptr构造的时候被初始化,此时weak_ptr保存了外部的shared_ptr对象,在后面使用this的时候只需要通过weak_ptr构造shared_ptr返回即可。 以TestA为例,我们的TestA类只需要继承enable_shared_from_this类,在需要返回this 的地方使用enable_shared_from_this的方法shared_from_this返回this的shared_ptr对象即可: #include #include class TestA: public boost::enable_shared_from_this { public: TestA() { std::cout <<"TestA::TestA()"<< std::endl; } ~TestA() { std::cout <<"TestA::~TestA()"<< std::endl; } boost::shared_ptr { std::cout <<"TestA::GetObj()"<< std::endl; boost::shared_ptr return p; } }; void func() { boost::shared_ptr boost::shared_ptr } 输出: TestA::TestA() TestA::GetObj() TestA::~TestA() 下面是enable_shared_from_this类的部分代码: template class enable_shared_from_this { //other method... public: shared_ptr { shared_ptr BOOST_ASSERT( p.get() == this ); return p; } private: mutable weak_ptr }; 由于enable_shared_from_this的weak_ptr成员在obj_a对象建立的时候就已经将其“记录”,所以在使用shared_from_this的时候,返回的是和obj_a对象“相同”的shared_ptr,因此不会造成二次释放。 总结一下,当被shared_ptr管理类的对象需要获取当前管理自己的shared_ptr的时候,需要继承enable_shared_from_this类,通过shared_from_this获取shared_ptr对象,不要自己通过构造shared_ptr enable_shared_from_this类非常简单,但看过源码的人恐怕会有一个疑问,enable_shared_from_this的构造函数是个空函数,其内部的weak_ptr成员是何时被this赋值的呢? 其实赋值过程非常简单,由于篇幅有限在这里只简要说明。对于上例来说,weak_ptr 被赋值确实是在obj_a对象建立的时候,只不过不在weak_ptr的构造函数,而是在shared_ptr的构造函数,在shared_ptr的构造函数中会回调enable_shared_from_this对象的_internal_accept_owner()方法: template void_internal_accept_owner( shared_ptr if( weak_this_.expired() ) { weak_this_ = shared_ptr } } weak_this_即为保存this的weak_ptr对象,回调后weak_this_已被初始化。至于shared_ptr是如何回调的还是请大家自行阅读shared_ptr源码。 线程安全问题 线程安全可能是很多使用者比较关心的问题,对于std::auto_ptr来讲,由于同一时间只有一个std::auto_ptr对象有原始指针的“拥有权”,不存在多std::auto_ptr的共享资源,因此不需要考虑std::auto_ptr的线程安全问题。 boost::shared_ptr由于使用了引用计数机制,多个boost::shared_ptr对象可能引用同一个计数器对象,必然会引入线程安全问题。从boost的1.33.0版本开始boost::shared_ptr使用了Lock-Free机制保证线程安全。 在多线程程序中,通常使用锁来保证共享数据安全,但过多的使用锁会带来线程阻塞,死锁等问题。使用锁过多的程序会使多线程的效率大大降低,极端情况下甚至可能低于单线 程程序,因此可以在一些场景中使用Lock-Free方式避免锁的使用。 Lock-Free(无锁编程)为一种多线程程序的编程技巧,它可以在不使用互斥锁解的前提下解决线程安全问题。举例来说,一个webserver使用多线程处理每个外部请求,为了记录webserver处理了多少次访问,在每次请求结束后,线程可能对一个记录请求次数的全局变量进行加一操作。在此场景中,全局变量为共享数据,多线程程序需要加锁来保证数据安全,但实际上对此变量的操作只是一条“加一”操作而已,若能保证“加一”操作的原子性,即可在无锁的前提下保证线程安全。 为了保证“加一”操作的原子性,需要使用一种叫做CAS(compare-and-swap)的原语,其模拟代码如下: bool CAS(intptr_t* addr, intptr_t oldv, intptr_t newv) atomically { if((*addr) == oldv) { *addr = newv; returntrue; } else { returnfalse; } } CAS使用addr指针所指向的内容与oldv比较,若相同则将newv的值赋值到*addr中,且整个过程是原子过程,不可被打断。上面代码只是CAS的原理代码,实际使用可能会是内嵌汇编等方式(在汇编中使用lock指令),很明显CAS是平台相关的,在Linux下GCC提供了__sync_bool_compare_and_swap函数族来实现CAS原语,在Windows下可以使用InterlockedCompareExchange及相关函数实现CAS。使用CAS原语的条件下实现“加一”操作见如下代码,由于__sync_bool_compare_and_swap是原子的,所以加一操作是安全的。int gCount = 0; int tmp = 0; do{ tmp= gCount + 1; }while(!__sync_bool_compare_and_swap(&gCount,gCount,tmp)); //若参数二的值与参数一指针中的内容相同,则执行gCount = tmp 使用Lock-Free解决shared_ptr的计数安全问题,从理论上讲是和如上场景是相同的,shared_ptr所使用的引用计数实际上是对变量的“加一”、“减一”操作,因此只要在数值变化时使用CAS,即可保证引用计数的线程安全性。shared_ptr的内部类sp_counted_base为计数实现的核心组件,如果希望了解Lock-Free在shared_ptr中是如何实现的,可以阅读此类,这里不再赘述。 Lock-Free技巧的核心是使用CAS原语保证赋值操作的原子性,但CAS原语多数是由汇编或内嵌汇编实现,因此Lock-Free技巧是与硬件平台相关的,甚至不同的CPU都可能造成未知的问题。若在某些硬件平台上不能使用Lock-Free,shared_ptr还提供了pthread的互斥方式,即使用pthread接口替代Lock-Free。使用方法是在头文件中加入宏定义:BOOST_SP_USE_PTHREADS。 如果能够确定应用程序不会使用多线程,可以定义宏BOOST_SP_DISABLE_THREADS,关闭所有线程互斥的相关操作,从而提高shared_ptr的使用效率。在boost的官网中有一节介绍boost::shared_ptr的线程安全问题,有兴趣的朋友可以阅读。 总结 本文对智能指针的概念及使用方法做了简要的介绍,并对std::auto_ptr、boost::shared_ptr、boost::weak_ptr做了比较详细的说明,由于boost::scoped_ptr与std::auto_ptr非常相似,因此没有做过多的讲解。 智能指针是现代C++编程中常用的功能,也是避免内存泄露的完美解决方案,但智能指针在使用中还需要对其优缺点及使用方式做充分了解,否则胡乱使用不但会使内存管理变得一塌糊涂,还会造成系统的不稳定。 特别说明这里介绍的是boost库中的shared_ptr并非std::tr1::shared_ptr,两种指针基本相同,且std::tr1::shared_ptr是由boost::shared_ptr代码而来,但毕竟是两个不同的库还是应该区别对待。 WritenByDangerman Guolisen@https://www.doczj.com/doc/355910949.html, 2013-7-1 C++智能指针(auto_ptr)详解 (2012-04-19 23:29:16) 转载▼ 标签: 分类:软件程序 c auto_ptr 智能指针 内存 it 智能指针(auto_ptr)这个名字听起来很酷是不是?其实auto_ptr 只是C++标准库提供的一个类模板,它与传统的new/delete控制内存相比有一定优势,但也有其局限。本文总结的8个问题足以涵盖auto_ptr的大部分内容。 1. auto_ptr是什么? auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个拥有者。当auto_ptr对象生命周期结束时,其析构函数会将auto_ptr对象拥有的动态内存自动释放。即使发生异常,通过异常的栈展开过程也能将动态内存释放。auto_ptr不支持new 数组。 2. auto_ptr需要包含的头文件? #include auto_ptr< int > api( new int( 33 ) ); 2) 拷贝构造 利用已经存在的智能指针来构造新的智能指针 auto_ptr< string > pstr_auto( new string( "Brontosaurus" ) ); auto_ptr< string > pstr_auto2( pstr_auto ); //利用pstr_auto来构造pstr_auto2因为一块动态内存智能由一个智能指针独享,所以在拷贝构造或赋值时都会发生拥有权转移的过程。在此拷贝构造过程中,pstr_auto将失去对字符串内存的所有权,而pstr_auto2将其获得。对象销毁时,pstr_auto2负责内存的自动销毁。 3) 赋值 利用已经存在的智能指针来构造新的智能指针 auto_ptr< int > p1( new int( 1024 ) ); auto_ptr< int > p2( new int( 2048 ) ); p1 = p2; 在赋值之前,由p1 指向的对象被删除。赋值之后,p1 拥有int 型对象的所有权。该对象值为2048。 p2 不再被用来指向该对象。 4. 空的auto_ptr 需要初始化吗? 通常的指针在定义的时候若不指向任何对象,我们用Null给其赋值。对于智能指针,因为构造函数有默认值0,我们可以直接定义空的auto_ptr如下: auto_ptr< int > p_auto_int; //不指向任何对象 5. 防止两个auto_ptr对象拥有同一个对象(一块内存) 因为auto_ptr的所有权独有,所以下面的代码会造成混乱。 int* p = new int(0); auto_ptr 服务器端代码:主要包括几个大的类。 Ice::Communicator类: Ice run time的主要进入点是由本地接口 Ice::Communicator来表示的。和在客户端一样,在你在服务器中做任何别的事情之前,你必须调用 Ice::initialize,对Ice run time进行初始化。Ice::initialize 返回一个智能指针,指向一个Ice::Communicator实例:int main(int argc, char * argv[]) { Ice::CommunicatorPtr ic = Ice::initialize(argc, argv); // ... } 在离开你的main函数之前,你必须调用Communicator::destroy。 destroy操作负责结束Ice run time。特别地,destroy会等待任何还在运行 的操作调用完成。此外,destroy还会确保任何还未完成的线程都得以汇合(joined),并收回一些操作系统资源,比如文件描述符和内存。决不要让你的main函数不先调用destroy就终止;这样做会导致不确定的行为。 Ice::Application类: #include 理论篇 ############################################################################### sp VC MFC实现SQL数据库ADO连接(完整版)关于vc6.0中实现ADO SQL数据库连接的文章网上虽然很多,但大多写的很繁琐,对于我们这样的菜鸟来说,还是很希望有一篇简单化的文章的。希望跟我一样的菜鸟们学得开心! 源代码文件链接: 概述: 要在一个vc工程中实现数据库的连接,最好的方式是在新建一个用于连接数据库的类比如ADOConn,需要连接数据库的时候将这个类实例化就可以了。 操作:新建一个ADOConn的对象,比如ADOConn ac;。然后再引用些对象就可以实现相应的操作了。 实践过程: (一)目的:封装一个自己的类。 在vc6.0中新建一个基于对话框的mfc工程,然后点击菜单栏->插入->类(mfc类)。 然后在“类的类型”中选择Generic Class,名称为ADOConn。 此后会在vc左边视窗的“Source Files”中多出一个ADOConn.cpp的文件,在"Header Files"中多出一个“ADOCon.h”的文件。 如果是用VS2013来创建工程的情况: VS2013中添加类: 右击myMFC(新建的项目)——添加——类——MFC类。 (二)打开ADOConn.h(即编辑ADOConn类的头文件) (1)在第一行加入(用于vc支持ado连接) 这句话意味使用msado15.dll动态连接库文件,里面就有ADO. 如果存在问题:用VS2010的C++导入ADO导入不了,提示无法打开源文件msado15.tlh”的问题。 解决办法很简单: 在生成菜单--- 重新生成 --- 即可。 原因是: 当编译器遇到#import语句时,它会为引用组件类型库中的接口生成包装类,#import语句实际上相当于执行了API涵LoadTypeLib()。 #import语句会在工程可执行程序输出目录中产生两个文件,分别为*.tlh(类型库头文件)及*.tli(类型库实现文件),它们分别为每一个接口产生智能指针,并为各种接口方法、枚举类型,CLSID 等进行声明,创建一系列包装方法。在没有经过编译器编译之前,文件还没有生成,所以,会出现错误提示。 (2)在class ADOConn类体中添加如下代码 【C++】智能指针类和OpenCV的Ptr模板类 2015-03-29 21:18 智能指针类 引用计数 智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。引用计数为0时,删除对象。 其基本使用规则是: 每次创建类的新对象时,初始化指针并将引用计数置为1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相应的引用计数的值。对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数的值(如果引用计数减至0,则删除对象),并增加右操作数所指对象的引用计数的值。最后,调用析构函数时,析构函数减少引用计数的值,如果计数减至0,则删除基础对象。 实现引用计数有两种经典策略:一是引入辅助类(包含引用计数型),二是使用句柄类(分离引用计数型)。 策略1:引用计数类 这个类的所有成员均为private。我们不希望用户使用U_Ptr 类,所以它没有任何public 成员。将HasPtr 类设置为友元,使其成员可以访问U_Ptr 的成员。 U_Ptr 类保存指针和使用计数,每个HasPtr 对象将指向一个U_Ptr 对象,使用计数将跟踪指向每个 U_Ptr 对象的HasPtr 对象的数目。U_Ptr 定义的仅有函数是构造函数和析构函数,构造函数复制指针,而析构函数删除它。构造函数还将使用计数置为1,表示一个HasPtr 对象指向这个U_Ptr 对象。 class U_Ptr { friend class HasPtr; int *ip; int use; U_Ptr(int *p):ip(p){} ~U_Ptr() { delete ip; } }; class HasPtr { public: HasPtr(int *p, int i):_ptr(new U_Ptr(p)),_val(i) {} HasPtr(const HasPtr& obj):_ptr(obj._ptr),_val(obj._val) { ++_ptr->use; } HasPtr& operator=(const HasPtr&); ~HasPtr() { 在你的代码中使用Boost智能指针 Smart Pointers to boost your code(By peterchen) 翻译 masterlee Download source files - 45.3kb 正文 智能指针能够使C++的开发简单化,主要是它能够像其它限制性语言(如C#、VB)自动管理内存的释放,而且能够做更多的事情。 1、什么是智能指针 智能指针是一种像指针的C++对象,但它能够在对象不使用的时候自己销毁掉。 我们知道在C++中的对象不再使用是很难定义的,因此C++中的资源管理是很复杂的。各种智能指针能够操作不同的情况。当然,智能指针能够在任务结束的时候删除对象,除了在程序之外。 许多库都提供了智能指针的操作,但都有自己的优点和缺点。Boost库是一个高质量的开源的C++模板库,很多人都考虑将其加入下一个C++标准库的版本中。 下面让我们看一个简单的例子: 2、首先介绍:boost::scoped_ptr 使用普通普通指针的时候,我们必须记住在函数退出的时候要释放在这个函数内创建的对象。当我们使用例外的时候处理指针是特别烦人的事情(容易忘记销毁它)。使用scoped_ptr 指针就能够在函数结束的时候自动销毁它,但对于函数外创建的指针就无能为力了。 优点:对于在复杂的函数种,使用scoped_ptr 指针能够帮助我们处理那些容易忘记释放的对象。也因此在调试模式下如果使用了空指针,就会出现一个断言。 3、引用指针计数器 引用指针计数器记录有多少个引用指针指向同一个对象,如果最后一个引用指针被销毁的时候,那么就销毁对象本身。 shared_ptr 就是Boost中普通的引用指针计数器,它表示可以有多个指针指向同一个对象,看下面的例子: void Sample2_Shared() { // (A)创建Csample类的一个实例和一个引用。 boost::shared_ptr ADO智能指针使用详解 一、ADO接口——Connection对象(连接对象)——对应智能指针为_ConnectionPtr 在使用ADO进行操作之前,必须使用AfxOleInit()函数来进行初始化。 使用BeginTrans、CommitTrans和RollbackTrans方法可以启动、提交和回滚一个处理事务。 通过操作the Errors 集合可以获取和处理错误信息,操作CommandTimeout属性可以设置连接的溢出时间,操作ConnectionString属性可以设置连接的字符串,操作Mode属性可以设置连接的模式,操作Provider属性可以指定OLE DB提供者。 (1)Open()方法:用于打开一个库连接 _ConnectionPtr智能指针的用法: ①首先定义一个Connection类型的指针,然后调用CreateInstance()来创建一个连接对象的实例,再调用Open()函数建立与数据源的连接。 ②在建立连接对象后,可以使用连接对象的Execute()函数来执行SQL命令。 _ConnectionPtr智能指针Open()方法的原型:Open(_bstr_t ConnectionString,_bstr_t UserID,_bstr_t Password,long Options) ①ConnectionString为连接字串,UserID是用户名,Password是登陆密码 ②Options是连接选项,可以是如下几个常量: 1> adModeUnknown 缺省,当前的许可权未设置 2> adModeRead 只读 3> adModeWrite 只写 4> adModeReadWrite 可以读写 5> adModeShareDenyRead 阻止其它Connection对象以读权限打开连接 6> adModeShareDenyWrite 阻止其它Connection对象以写权限打开连接 7> adModeShareExclusive 阻止其它Connection对象打开连接 8> adModeShareDenyNone 阻止其它程序或对象以任何权限建立连接 (2)Execute()方法:用于执行SQL语句 函数原型:_RecordsetPtr Connection::Execute(_bstr_t CommandText,VARIANT* RecordsAffected,long Options) 参数: ①CommandText是命令字串,通常是SQL命令, ②RecordsAffected是操作完成后所影响的行数 ③Options表示CommandText中内容的类型,可以取下列值之一: 1> adCmdText 表明CommandText是文本命令 2>adCmdTable 表明CommandText是一个表名 什么是编译? 编译器是将一种语言翻译为另一种语言的计算机程序。编译器将源程序(source language)编写的程序作为输入,而产生用目标语言(target language )编写的等价程序。通常地,源程序为高级语言(high-level language ),如C或C + + ,而目标语言则是目标机器的目标代码(object code,有时也称作机器代码(machine code )),也就是写在计算机机器指令中的用于运行的代码。这一过程可以表示为:源程序→编译器→目标程序 什么是java? Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun Microsystems 公司于1995年5月推出的Java程序设计语言和Java平台(即JavaSE, JavaEE, JavaME)的总称。Java 技术具有卓越的通用性、高效性、平台移植性和安全性,广泛应用于个人PC、数据中心、游戏控制台、科学超级计算机、移动电话和互联网,同时拥有全球最大的开发者专业社群。在全球云计算和移动互联网的产业环境下,Java更具备了显著优势和广阔前景。 什么是c语言 C语言是一种计算机程序设计语言。它既具有高级语言的特点,又具有汇编语言的特点。它由美国贝尔研究所的D.M.Ritchie于1972年推出。1978后,C语言已先后被移植到大、中、小及微型机上。它可以作为工作系统设计语言,编写系统应用程序,也可以作为应用程序设计语言,编写不依赖计算机硬件的应用程序。它的应用范围广泛,具备很强的数据处理能力,不仅仅是在软件开发上,而且各类科研都需要用到C语言,适于编写系统软件,三维,二维图形和动画。具体应用比如单片机以及嵌入式系统开发。 什么是c++语言 l l 对于要解决实际问题的程序员而言,C++使程序设计变得更有乐趣; l l C++是一门通用目的的程序设计语言,它: ——是一个更好的C; ——支持数据抽象; ——支持面向对象程序设计; ——支持范型程序设计。 对范型程序设计的支持在C++设计的后期才被作为一个明确、独立的目标来实现。而在C++演化过程的大部分时间里,我一直把范型程序设计以及支持它的语言特性划归在“数据抽象”的大标题之下。 C++ 智能指针详解 一、简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。 用智能指针便可以有效缓解这类问题,本文主要讲解参见的智能指针的用法。包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、 boost::scoped_array、boost::shared_array、boost::weak_ptr、 boost::intrusive_ptr。你可能会想,如此多的智能指针就为了解决new、delete 匹配问题,真的有必要吗?看完这篇文章后,我想你心里自然会有答案。 下面就按照顺序讲解如上 7 种智能指针(smart_ptr)。 二、具体使用 1、总括 对于编译器来说,智能指针实际上是一个栈对象,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智能指针都重载了“operator->”操作符,直接返回对象的引用,用以操作对象。访问智能指针原来的方法则使用“.”操作符。 访问智能指针包含的裸指针则可以用 get() 函数。由于智能指针是一个对象,所以if (my_smart_object)永远为真,要判断智能指针的裸指针是否为空,需要这样判断:if (my_smart_object.get())。 智能指针包含了 reset() 方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。 我们编写一个测试类来辅助分析: class Simple { public: Simple(int param = 0) { number = param; std::cout << "Simple: " << number << std::endl; } ~Simple() { std::cout << "~Simple: " << number << std::endl; 智能指针:强指针sp,弱指针wp,轻量级指针LightRefBase。 相关文件:RefBase.h,RefBase.cpp,StrongPointer.h(注:参考代码android 4.2.2)。RefBase.h:定义了RefBase类定义,wp模板类定义和实现,以及LightRefBase类定义。RefBase.cpp:定义了RefBase类实现以及RefBase的嵌套类weakref_type的实现。StrongPointer.h:定义了sp模板类定义和实现。 RefBase类主要方法如下: void RefBase::incStrong(const void* id) const { weakref_impl* const refs = mRefs; refs->incWeak(id); // 增加一次弱引用计数 refs->addStrongRef(id); // 空函数 // 原子操作,增加一次强引用计数,返回的是refs->mStrong执行加1操作之前的值const int32_t c = android_atomic_inc(&refs->mStrong); ALOG_ASSERT(c > 0, "incStrong() called on %p after last strong ref", refs); // 第一次执行,c的值为INITIAL_STRONG_V ALUE if (c != INITIAL_STRONG_V ALUE) {//从第二次开始执行后,此条件都成立,直接返回return; } // 执行操作,refs->mStrong + (-INITIAL_STRONG_V ALUE),第一次执行后强引用计数refs->mStrong值变为1 android_atomic_add(-INITIAL_STRONG_V ALUE, &refs->mStrong); refs->mBase->onFirstRef(); //第一次执行会调用该方法,子类可以覆盖该方法。 } void RefBase::decStrong(const void* id) const { weakref_impl* const refs = mRefs; refs->removeStrongRef(id); // 空函数 // 原子操作,强引用计数减1,返回的是执行减1操作之前的值 const int32_t c = android_atomic_dec(&refs->mStrong); ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs); if (c == 1) { refs->mBase->onLastStrongRef(id); // 子类可覆盖该方法 // mFlags值缺省为0 if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) { delete this; } } refs->decWeak(id); // 弱引用计数减1 } void RefBase::forceIncStrong(const void* id) const { 第1章Boost程序库总论 1. 使用Boost,将大大增强C++的功能和表现力 第2章时间与日期 1. timer提供毫秒级的计时精度,内部是通过std::clock 取时间的 2. progress_timer 自动打印某生命周期的执行时间 3. 原则上程序库的代码是不应该被用户修改的 4. progress_display 可以在控制台上显示程序的执行进度 5. date_time 库能很好的表示日期时间概念,并能和C的时间结构tm进行友好互转 6. date类提供年月日和星期几的概念。data可通过from_string 或 from_un delimited_stri ng 从字符串解析而来,可通过to_simple_stri ng 、 to_iso_string 、to_extended_iso_string 转换为字符串。(精度至U天的DateTime ) 7. day_clock是精度到天的时钟 8. date_duration 表示的是天精度的时间间隔概念,别名为days,另外还有years、 mon ths 、weeks 9. date_period 表示两个date之间的日期区间(精度到天的TimeSpan ) 10. date_iterator 、week_iterator 、month」terator 和year_iterator 是时间的迭 代器 11. boost:: greorian ::gregorian_calendar 中有实用静态方法:is_leap_year、 en d_of_m on th_day 12. time_duration 表示微妙或纳秒级的时间概念,几个实用子类:hours、minutes、 seconds 、millisec/milliseconds 、microsec/microseconds 、 nano sec/ nanno sec onds VC++中操作XML(MFC、SDK) XML在Win32程序方面应该没有在Web方面应用得多,很多Win32程序也只是用XML来存存配置信息而已,而且没有足够的好处的话还不如用ini。VC++里操作XML有两个库可以用:MSXML和XmlLite。MSXML又细分了两种接口:DOM和SAX2。XP没自带有XmlLite,只自带有2.x、3.x版的MSXML,不支持SAX2(需要MSXML 4.0以上),所以优先使用DOM。 DOM是以COM形式提供的,VC++里调用DOM可以分3种方法: 1、MFC里用CComPtr调用 2、SDK里直接调用DOM接口 3、SDK里用智能指针调用 这3种方法本质上是一样的,区别只不过在于需要编码的多少而已,用CComPtr可以极大的简化代码,下面是几个例子。 例子stocks.xml: 1. 定义 一个智能指针就是一个C++的对象,这对象的行为像一个指针,但是它却可以在其不需要的时候自动删除。注意这个“其不需要的时候”,这可不是一个精确的定义。这个不需要的时候可以指好多方面:局部变量退出函数作用域、类的对象被析构……。所以boost定义了多个不同的智能指针来管理不同的场景。 2. Boost::scoped_ptr 6. cout << "test print now" < 1.Framework 2.Module(空的,用来放workbenchs,components,workbench要把原来的module清掉,components不用,它是直接在上面加的东西) 3.Framework:public 用来放盛workbench的module头文件 Private:这个是加进来workbench怎么继承上面的一个类 还一个是生成一个workebench的出去文件 3.IdentityCard 用来放外部或系统的API所属的外部框架定义 4.mk文件时定义外部或系统的API所属的外部模块定义 查看dico 外部框架外部模块在相应的文件定义是关键 5.这里有三个文件 CA TNIs可以定义此Workbench以及其工具条、菜单的所有名字(Title),定义格式为:TestWb.Title=”test”; CA TRsc可以定义此Workbench的图标(Icons)和其放置路径(Category)(这里放模块) MyWorkbench.Icon.NormalPnl="Clouds"; MyWorkbench.Icon.NormalCtx="Clouds"; MyWorkbench.Icon.NormalRep="Clouds"; MyAddin.CATNls中添加 MybenchTlb.Title="月亮"; MyCmdHeader.CA TRsc中添加 MyCmdHeader.point.Icon.Normal="mycmd"; 另一个CA TNIs文件是实现workbench的命令头文件 6..dico文件定义的是链接库 还一个是工厂定义 新建一个workbench的时候,首先要以workbench名声明一个工厂 然后CATApplicationFrame框与工厂进行库连接 给新建的workbench 与它下面的配置文件接口进行库连接 7.在Module中的trsc文件是A TIE is the object that links the interface and the implementation TIE以trsc的形式存在 在这个workbench所在的模块里面有src里面是本模块的CPP文件集合,local interface里面是本模块的.h文件集合,包括 ①上面提到的trsc文件,用来链接接口和现实的对象的的,主要是两组继承,工厂的继 承和addin的继承2 ②还有从基工厂类上继承一个wokbench工厂所需要的cpp和.h文件。还一个是创建一个 新工厂1 ,1 || 1 ,1 ③还有将执行的命令用MacDefineHeader宏定义当前的workbench命令,也有.h文件和 CPP文件1,1 ④实现现实的对象需要header.h workshop.h 还有就是就是从最初基类 CA TBaseUnknown继承下来的派生类1,3 ⑤还有一个是interface此接口是定义addin接口文件,用来继承库中的addin 1,1 8.setting CATIA 可以启动看到workbench了 9.添加工具条 1引言 OPC(OLE for Process Control)是一个工业标准,他是许多世界领先的自动化和软、硬件公司与微软公司合作的结晶。管理该标准的组织是OPC基金会。该基金会的会员单位在世界范围内超过150个,包括了世界上几乎全部的控制系统、仪器仪表和过程控制系统的主要供应商。OPC 技术建立了一组符合工业控制要求的接口规范,将现场信号按照统一的标准与SCADA、HMI等软件无缝连接起来,同时将硬件和应用软件有效地分离开。只要硬件开发商提供带有OPC接口的服务器,任何支持OPC接口的客户程序均可采用统一的方式对不同硬件厂商的设备进行存取,无须重复开发驱动程序。如果希望将数据引入数据库进行统计分析,就要进行客户端开发。 2客户程序的设计方法与比较 客户程序的设计主要是指客户程序中OPC接口部分的设计。客户程序本身可以完成很多复杂的数据处理与显示功能,但需要通过OPC接口部分访问OPC服务器,对现场数据进行存取。 开发OPC、Data、Access、Client之前,要弄清服务器的大体情况,比如需要访问的服务器是否提供自动化接口、服务器的OPC的版本等,到目前为止,OPC有1.0和2.0两个版本,两个版本的接口定义不同,2.0版是对1.0的改进,但不兼容。 OPC客户端的主要任务: ①创建服务器对象。 ②建立与服务器的连接。 ③浏览OPC服务器的功能。客户程序需要创建OPC基金会提供的OPC服务器浏览器对象(OPCServerList)再通过该对象的IOPCServerList接口获得OPC服务器名称的列表;可以通过枚举注册表中包含“OPC”子键的程序名来浏览符合OPC数据存取规范的服务器,但效率较低。 ④通过OPC接口读写数据。 ⑤断开连接。 C++箴言:将new出来的对象存入智能指针 不要忘记使用对象管理资源的至理名言,processWidget 为处理动态分配的Widget 使用了一个智能指针。 假设我们有一个函数取得我们的处理优先级,而第二个函数根据优先级针 Widget 使用了一个智能指针(在此,是一个 tr1::shared_ptr)。 个裸指针(raw pointer)应该是显式的,所以不能从一个由 "new Widget" 返回的裸指针隐式转型到 processWidget 所需要的 tr1::shared_ptr.下面的代 还是可能泄漏资源。下面就来说明这是如何发生的。 在编译器能生成一个对 processWidget 的调用之前,它们必须传递实际参数来计算形式参数的值。第二个实际参数不过是对函数 priority 的调用,但是第一个实际参数("std::tr1::shared_ptr auto_ptr到底能不能作为容器的元素? 【摘要】对C++语言本身来说,它并不在乎用户把什么类型的对象作为STL容器的元素,因为模板类型参数在理论上可以为任何类型。比如说STL容器仅支持“值”语义而不支持“引用(&)”语义,并非因为模板类型参数不能为引用,而是因为如果容器元素为引用类型,就会出现“引用的引用”、“引用的指针”等C++语言不支持的语法和语义。智能指针是一种模拟原始指针行为的对象,因此理论上也可以作为容器的元素,就象原始指针可以作为容器元素一样。但是智能指针毕竟是一种特殊的对象,它们在原始指针共享实值对象的基础能力上增加了自动销毁实值对象的能力,如果将它作为容器的元素,可能导致容器之间共享元素对象实值,这不仅不符合STL容器的概念和“值”语义,也会存在安全隐患,同时也会存在许多应用上的限制,特别是象STL中的auto_ptr这样的智能指针。本文深入地阐述了auto_ptr这种较简单的智能指针“可以”或者“不可以”作为容器元素的根本原因,以及它作为容器元素会存在的限制和带来的问题,最后说明auto_ptr存在的真正意义、正确的使用方法以及它的替代品——带有引用计数能力的智能指针,当容器之间需要共享元素对象时,或者程序中存在大量的指针传递而担心资源泄漏时,这样的智能指针就特别有用。 【关键字】auto_ptr 容器智能指针 一、引言 Scott Meyers在《More Effective C++》[3]一书中对智能指针及其相关问题(构造、析构、复制、提领、测试以及类型转换等)作了深入的分析,其中也提到“STL的auto_ptr这种在复制时会把对实值对象的拥有权转交出去的智能指针不宜作为STL容器的元素”,而且在他的《Effective STL》[4]Item 8中明确指出了这一点。Nicolai M.Josuttis的《The C++ Standard Library》[5]中有一节专门针对auto_ptr的阐述也指出“auto_ptr不满足STL标准容器对元素的最基本要求”。但是他们都是从容器的需求、语义以及应用的安全性来阐述,而没有从语言的静态类型安全性和auto_ptr的实现方案角度深入地分析其原因,因此有些读者看了之后可能仍然不明就里:它是如何不满足容器需求的?它是如何违反C++的静态类型安全性从而避免误用的? 我们知道,可以作为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),但实际上拷贝赋值的要求也不是强制的,原auto_ptr智能指针
ICE代码详解
(完整word版)Mstar理论及实践篇
MFC实现SQL数据库ADO连接
C++智能指针
智能指针
ADO智能指针使用详解
软件类笔试试题
C++ 智能指针详解
android智能指针(wp、sp)学习总结
C++boost库总结
VC++中操作XML(MFC、SDK)
boost智能指针的使用
CATIA二次开发详解点汇总
OPC客户端的实现
C 箴言将new出来的对象存入智能指针解析
auto_ptr到底能不能作为容器的元素