《C++应用程序性能优化::第五章动态内存管理》学习和理解
说明:《C++应用程序性能优化》 作者:冯宏华等 2007年版。
2010.8.29
cs_wuyg@126.com
这一章不错,之前对new和delete的理解并不是很深。虽然学C++primer的时候懂一点智能指针,但是没学过boost的智能指针,这书里边都讲到了,找了下资料,很容易理解。这本书适合有一定开发经验的开发人员,我这个还没有开发经验的菜鸟还是能从中获得很多知识。
一、 operator new/delete
C++标准中3.7.3中规定,C++实现通过全局“allocation functions”new/new []和“deallocation functions”delete/delete[]来提供动态内存的访问和管理。
众所周知,new试图分配给定大小size的内存。如果成功,返回获得内存块的起始地址。这里需要指出的是,C++标准中没有规定是否要对获得的内存进行初始化。这意味着,如果开发人员没有显式的对获得的内存赋值,那么它们的初始值完全取决于编译器的实现。
operator new有多种形式,placement new(例如char *p = new(buff) char[100])可以指定要申请的空间在哪个地方(就在buff里)。
应用程序必须处理内存分配失败的情况。通过可以通过捕获std::bad_alloc异常或者返回值检查内存分配是否成功,而更好的方法是使用C++中的new_handler()函数。
C++标准中对内存分配失败有明确的规定,系统会调用当前安装的new_handler()函数。这个错误处理函数是通过set_new_handler()安装到系统上的,C++规定new_handler要执行下列操作中的一种:
(1) 使new操作有更多的内存可用,然后返回
(2) 抛出一个bad_alloc或其派生的异常
(3) 调用abort()或者exit()退出。
二、 自定义operator new/delete
当应用程序需要用同一的机制来控制数据的内存分配情况,并且不想使用系统提供的内存管理机制时,可以通过重写自己的全局operator new/delete来实现。一般对于一些内存要求较高的应用程序可能会采用这种方式。此外,有时为了调试内存分配情况,也会实现全局的operator new/delete,增加一些特殊处理,如产生log等来方便调试和排错。《Effective C++》中指出,自己重写operator new/delete时,很重要的一点就是函数提供的行为要和系统默认的operator new一致。
当自定义全局的operator new/delete时,程序中的所有内存分配释放将使用统一的方式。然而在某些情况下,程序希望创建不同的类对象时使用不同的内存分配方式,尤其是在一些要求内存分配效率的程序中。为此,可以通过类成员函数形式的operator new/delete重载来为一些特定的类实现这个类自己的operator new/delete,而其他则保持使用系统默认的operator new/delete。在实际开发中,如果要自定义operator new,最好也要实现自定义delete。
当某个类自定义了operator new/delete,它的派生类也会默认继承使用基类自定义的operator new/delete。如果想让派生类使用全局的new/delete,那么可以在基类的operator new中判断参数的大小,当是基类的时候就使用自定义的,当不是基类的时候就是用全局的。也可以通过另一种方式实现,让派生类重写operator new,在重写的函数里边调用全局的new。
三、 避免内存泄漏
一个复杂的C++程序中最容易出现的问题是内存泄漏。即是忘记释放申请的内存,造成程序占用的内存不断上升,系统性能会不断下降,甚至会内存耗尽而导致程序崩溃。相比java,C++没有自动的垃圾回收,但是C++语言还是提供了足够强大和灵活的机制。可以将new和delete操作封装到一个类里边,在构造时分配内存,在析构时释放内存,这样就不会有内存泄漏(资源申请、释放跟构造、析构绑定,这是RAII:resource acquisition is initialization)。
把new和delete操作封装到类里边,要考虑到拷贝构造函数和重载赋值操作的问题,因此还需要增加一些支持。因为当使用默认的拷贝构造函数时,执行位拷贝,使得两个不同对象里边的指针指向同一个堆区的空间,这样当某个对象析构的时候,就会释放掉该堆空间,这样就出问题了,可能同一个空间被释放两次。同样赋值操作也存在这样的问题,而且赋值操作的右操作数对象的堆空间可能没有被释放。
一种解决的办法是把拷贝构造函数和赋值操作私有了,使得赋值或使用拷贝构造函数的操作都会被编译器认为不合法。这样就不会出现两个不同的对象指向同一个堆地址空间(boost的scoped_ptr就是这样),也就是说不同的对象不会共享同一个堆地址空间。将那两个函数私有了,会存在很多的限制。
另一种解决方法是,增加一个引用计数,用于统计有多少个对象指向某块相同的堆区域,刚构造一个对象时就在堆上分配一块空间,并让引用计数值为1,当发生拷贝构造,或者该对象作为右值对另一个对象赋值时就让引用计数加1,而不会去分配堆空间。同样的当某个对象不再指向那块堆区域时,引用计数就减1。对象的析构操作和赋值操作都有可能会使得对象的引用计数减为0,所以在这两个函数里边必须判断什么时候引用计数为0,一旦为0,就释放那块堆区域,不为0就不释放。
四、 智能指针
通过对申请和释放内存的封装是避免内存泄漏的基本思路,要使得有通用性、方便还有很多要考虑。现在的很多C++库提供了称为“智能指针”的模板类。使得开发人员可以方便的管理动态内存,而不必担心内存泄漏的问题。
智能指针就是存储指向动态内存/对象的指针和类,其使用和工作机制与C++内置的指针非常相似,而最大的不同是会在适当的时间自动删除指向的内存和对象。此外,一般还提供reset()方法,可以显式释放内存或销毁对象。它们使用operator->和operator*来生成原始指针,这样智能指针看上去就像一个普通指针。但它们要考虑很多因素,例如所有权、线程安全,以及异常安全问题等。
std::auto_ptr的使用。如果发生拷贝,那么被拷贝的源对象对象所指向的堆空间所有权就归目的对象所有,也就说如果一个对象被另一个对象拷贝了,那么那个被拷贝的对象就是“空”的了。不可以用于数组、STL容器。
boost::scoped_ptr的使用。可以通过reset()显式销毁对象。与std::auto_ptr不同,不会发生所有权转移,因为它把拷贝构造函数和赋值重载放到了私有成员里。不可以用于数组、STL容器。
boost::scoped_array的使用。是scoped_ptr的数组形式扩展。
boost::shared_ptr的使用。可拷贝、可赋值。类似于之前的使用了“引用计数”的类。可以通过reset()来解除某个对象对某堆空间区域的指向,这样就使得引用计数减1。可以使用use_count()方法检查当前指向所管理的对象的shared_ptr的个数。
boost::shared_array的使用。支持共享指针的数组形式。会使用delete[]来释放对象。
boost::weak_ptr的使用。跟shared_ptr配合使用,是一种可以在shared_ptr使用之前进行安全检查的类。可以用于多线程的安全访问。weak_ptr的lock()方法返回一个shared_ptr对象,如果该对象存在,则可以使用返回的对象,不然返回的是空指针。
正确的使用智能指针,可以减少开发人员的错误,提高开发效率;如果使用不正确,也将给程序带来灾难。这里的关键是正确地理解每种智能指针的语义,了解其优点和限制。然后结合程序的需求,选择合适的智能指针或者避免使用智能指针。
另外,“侵入式智能指针”跟上边说到的不是同类,可以了解下。
长时间的看同一本书,有些烦躁。
2010-8-30
cs_wuyg@126.com
因为网络问题,没法下载到boost库的安装文件。还好Code::Blocks绿色版里边有.hpp文件和编译好的.lib。。
附上书上的或另外找的学习代码:
1、new_handler的使用
//new_handler的使用.cpp //2010.8.28 //coder:cs_wuyg@126.com //参考P151 //Code::Blocks vs #include <iostream> using namespace std; ///////////////////////////////////////////////////////////////// char *gPool = NULL; void my_new_handler(); ///////////////////////////////////////////////////////////////// int main() { set_new_handler(my_new_handler); gPool = new char[100*1024*1024]; if (gPool != NULL) { cout << "Preserve 101MB memeory at " << hex << (void*)gPool << endl; } char *p = NULL; for (int i = 0; i < 20; ++i) { p = new char[100*1024*1024]; cout << i+1 << "*100M, p = " << hex << (void*)p << endl; } cout << "Done" << endl; return 0; } ///////////////////////////////////////////////////////////////// /*new_handler函数*/ void my_new_handler() { if (gPool != NULL) { cout << "try to get more memory" << endl; delete[] gPool; gPool = NULL; return; } else { cout << "I can not help..." << endl; throw bad_alloc(); } return ; } /* Preserve 101MB memeory at 00430020 1*100M, p = 06840020 2*100M, p = 0CC50020 3*100M, p = 13060020 4*100M, p = 19470020 5*100M, p = 1F880020 6*100M, p = 25C90020 7*100M, p = 2C0A0020 8*100M, p = 324B0020 9*100M, p = 388C0020 a*100M, p = 3ECD0020 b*100M, p = 450E0020 c*100M, p = 4B4F0020 d*100M, p = 51900020 e*100M, p = 57D10020 f*100M, p = 5E120020 10*100M, p = 64530020 11*100M, p = 6A940020 12*100M, p = 70D50020 try to get more memory 13*100M, p = 00430020 I can not help... */
2、placement new的使用
//placement new.cpp //2010.8.28 //coder:cs_wuug@126.com //参考P153 //Code::Blocks10.05 VS #include <iostream> using namespace std; int main() { char buffer[1024]; char *p = new(buffer) char[20]; cout << "buffer: " << (void*)buffer << endl; cout <<"p : " << (void*)p << endl; system("pause"); return 0; } /* buffer: 0012FB68 p : 0012FB68 请按任意键继续. . . */
3、自定义类operator new,delete
//自定义类operator new,delete.cpp //2010.8.28 //coder:cs_wuyg@126.com //参考P160 //自定义类operator new/delete的使用,基类使用了自定义的new、delete但是派生类不使用自定义的new、delete //Code::blocks VS #include <iostream> using namespace std; ///////////////////////////////////////////////////////////////////////// class Base { public: int m_Value; static void* operator new(size_t n); static void* operator new(size_t n, char* file, int line); }; void* Base::operator new(size_t n, char* file, int line) { cout << "size: " << n << endl << "new at " << file << ", " << line << endl; return ::operator new(n); } void* Base::operator new(size_t n) { cout << "size: " << n << endl; return ::operator new(n); } class Derived : public Base { int m_nDerivedValue; public: static void* operator new(size_t n);//重写 static void* operator new(size_t n, char* file, int line);//重写 }; void* Derived::operator new(size_t n, char* file, int line) { return ::operator new(n); } void* Derived::operator new(size_t n) { return ::operator new(n); } ///////////////////////////////////////////////////////////////////////// int main() { cout << "----------test1----------" << endl; Base *p1 = new Base; Base *p2 = new(__FILE__, __LINE__) Base; cout << "----------test2----------" << endl; Derived *p3 = new Derived; Derived *p4 = new(__FILE__, __LINE__) Derived; char *buffer = new char[10]; system("pause"); return 0; } /* ----------test1---------- size: 4 size: 4 new at D:\C++应用程序性能优化学习笔记\第五章动态内存管理\自定义类operator new,delete.cpp, 51 ----------test2---------- 请按任意键继续. . . */
4、“引用计数”类
//引用计数类.cpp //2010.8.28 //coder:cs_wuyg@126.com //参考P166 //Code::Block vs #include <iostream> using namespace std; class Base { private: char* m_buf; size_t m_nsize; int* m_count; public: //constructor Base(size_t n = 1) { m_buf = new char[n]; m_count = new int; m_nsize = n; *m_count = 1; cout << "constructor----count is: " << *m_count << endl; } //copy constructor Base(const Base& bobj) { m_nsize = bobj.m_nsize; m_buf = bobj.m_buf; m_count = bobj.m_count; (*m_count)++; cout << "copyconstructor----count is: " << *m_count << endl; } //operator= Base& operator=(const Base& bobj) { if (m_buf == bobj.m_buf) { return *this; } delete this; m_buf = bobj.m_buf; m_nsize = bobj.m_nsize; m_count = bobj.m_count; (*m_count)++; cout << "count is: " << *m_count << endl; return *this; } //destructor ~Base() { (*m_count)--; cout << "count is:" << *m_count << endl; if (*m_count == 0) { cout << "destructor" << endl; delete[] m_buf; delete m_count; } } char* getbuf() { return m_buf; } }; int main() { Base obj1(100); Base obj2(200); //测试copy constructor Base obj3 = obj1; //测试operator= obj2 = obj1; cout << "-------Test-------" << endl; strcpy(obj1.getbuf(), "cs_wuyg"); cout << obj2.getbuf() << endl; cout << obj3.getbuf() << endl; system("pause"); return 0; } /* constructor----count is: 1 constructor----count is: 1 copyconstructor----count is: 2 count is:0 destructor count is: 3 -------Test------- cs_wuyg cs_wuyg 请按任意键继续. . . count is:2 count is:1 count is:0 destructor */
5、智能指针std::auto_ptr
//智能指针auto_ptr.cpp //2010.8.28 //coder:cs_wuyg@126.com //参考P170 //Code::Blocks 10.05 VS2005/2008 #include <iostream> #include <string> #include <memory>//使用auto_ptr using namespace std; class Base { public: Base(size_t m_nSize = 10) : m_nSize(m_nSize) { cout << "constructor" << endl; } ~Base() { cout << "destructor" << endl; } void foo() { cout << "-----foo()-----" << endl; cout << "m_nSize is : " << m_nSize << endl; } private: size_t m_nSize; }; int main() { //定义指向string的智能指针 auto_ptr<string> pString(new string("hello, auto_ptr")); cout << *pString << endl; //定义指向Base类对象的智能指针 auto_ptr<Base> pBase(new Base(100)); pBase->foo(); system("pause"); return 0; } /* 测试: hello, auto_ptr constructor -----foo()----- m_nSize is : 100 请按任意键继续. . . destructor */
6、作用域智能指针boost::scoped_ptr
//scoped_ptr.cpp //2010.8.29 //coder:cs_wuyg@126.com //参考P173 #include <iostream> #include <boost\scoped_ptr.hpp> using namespace std; class Base { public: Base(size_t m_nSize = 10) : m_nSize(m_nSize) { cout << "constructor" << endl; } ~Base() { cout << "destructor" << endl; } void foo() { cout << "-----foo()-----" << endl; cout << "m_nSize is : " << m_nSize << endl; } private: size_t m_nSize; }; int main() { boost::scoped_ptr<Base> pBase(new Base); system("pause"); return 0; } /* constructor 请按任意键继续. . . destructor */
7、boost::scoped_array
//scoped_array.cpp //coder:cs_wuyg@126.com //2010.8.29 //参考P173 //测试结果表明,auto_ptr用于数组会有内存泄露 #include <iostream> #include <memory> #include <boost/scoped_array.hpp> using namespace std; class Base { public: Base(size_t m_nSize = 10) : m_nSize(m_nSize) { cout << "constructor" << endl; } ~Base() { cout << "destructor" << endl; } void foo() { cout << "-----foo()-----" << endl; cout << "m_nSize is : " << m_nSize << endl; } private: size_t m_nSize; }; void boostarray() { boost::scoped_array<Base> pBase(new Base[2]); } void autoarray() { auto_ptr<Base> pBase2(new Base[2]); } int main() { cout << "--------boost array--------" << endl; boostarray(); cout << "--------boost array--------" << endl; cout << "--------auto array--------" << endl; autoarray(); cout << "--------auto array--------" << endl; system("pause"); return 0; } /* --------boost array-------- constructor constructor destructor destructor --------boost array-------- --------auto array-------- constructor constructor destructor --------auto array-------- */
8、boost::shared_ptr
//share_ptr.cpp //2010.8.29 //coder:cs_wuyg@126.com //共享智能指针 //参考P174 //Code::Blocks vs2005/2008 #include <iostream> #include <string> #include <boost/shared_ptr.hpp> using namespace std; class Base { public: Base(size_t m_nSize = 10) : m_nSize(m_nSize) { cout << "constructor" << endl; } ~Base() { cout << "destructor" << endl; } void foo() { cout << "-----foo()-----" << endl; cout << "m_nSize is : " << m_nSize << endl; ++m_nSize; } private: size_t m_nSize; }; void shareptr() { //试试Base对象使用智能指针 cout << "------------------" << endl; boost::shared_ptr<Base> pBase1(new Base); boost::shared_ptr<Base> pBase2(pBase1); pBase1->foo(); pBase2->foo(); cout << "------------------" << endl; //测试使用use_count、reset cout << "use count is : " << pBase2.use_count() << endl; pBase1.reset(); cout << "use count is : " << pBase2.use_count() << endl; pBase2.reset(); //试试string对象使用智能指针 cout << "------------------" << endl; boost::shared_ptr<string> pstr1(new string("cswuyg")); boost::shared_ptr<string> pstr2(pstr1); cout << pstr1 << endl; cout << pstr2 << endl; } int main() { shareptr(); system("pause"); return 0; } /* ------------------ constructor -----foo()----- m_nSize is : 10 -----foo()----- m_nSize is : 11 ------------------ use count is : 2 use count is : 1 destructor ------------------ 003844A0 003844A0 请按任意键继续. . . */
9、boost::shared_array
//共享数组指针.cpp //2010.8.29 //coder:cs_wuyg@126.com //练习共享数组智能指针。boost::shared_array //Code::Blocks vs2005/2008 #include <iostream> #include <string> #include <boost/shared_array.hpp> using namespace std; class Base { public: Base(size_t m_nSize = 10) : m_nSize(m_nSize) { cout << "constructor" << endl; } ~Base() { cout << "destructor" << endl; } void foo() { cout << "-----foo()-----" << endl; cout << "m_nSize is : " << m_nSize << endl; } private: size_t m_nSize; }; int main() { boost::shared_array<Base> pstr1(new Base[2]); boost::shared_array<Base> pstr2(pstr1); cout << &pstr2[0] << endl; cout << &pstr2[1] << endl; cout << "----------------" << endl; cout << &pstr1[0] << endl; cout << &pstr1[1] << endl; system("pause"); return 0; } /* constructor constructor 00382E74 00382E78 ---------------- 00382E74 00382E78 请按任意键继续. . . destructor destructor */
10、boost::weak_ptr
//weak_ptr.cpp //coder:cs_wuyg@126.com //2010.8.29 //参考P179 //Code::Blocks vs2005/2008 #include <iostream> #include <memory> #include <boost/weak_ptr.hpp> #include <boost/shared_ptr.hpp> using namespace std; class Base { public: Base(size_t m_nSize = 10) : m_nSize(m_nSize) { cout << "constructor" << endl; } ~Base() { cout << "destructor" << endl; } void foo() { cout << "-----foo()-----" << endl; cout << "m_nSize is : " << m_nSize << endl; ++m_nSize; } private: size_t m_nSize; }; void testptr() { boost::shared_ptr<Base> pBase1(new Base); boost::weak_ptr<Base> pBase2(pBase1); // pBase1.reset();//加上这句作测试,weak_ptr的作用,使用weak_ptr的lock可以保证shared_ptr对象存在时才执行。 boost::shard_ptr<Base> pBase2_1(pBase2->lock());// 提升为shard_ptr,线程安全.保证下面执行时的执行引用计数至少为1. if (pBase2_1) { pBase2_1->foo(); } else { cout << "Base obj is delete!!" << endl; } try { boost::shared_ptr<Base> pBase3(pBase2); pBase3->foo(); }catch(boost::bad_weak_ptr) { cout << "Base obj is delete, can not construct shared_ptr!!" << endl; } } int main() { testptr(); system("pause"); return 0; } /* constructor -----foo()----- m_nSize is : 10 -----foo()----- m_nSize is : 11 destructor 请按任意键继续. . . */