[toc]
Resource Management
如内存,互斥锁,数据库连接,网络sockets……
重要的是,当你不再使用它了,应该将它还给系统。
13. Use objects to manage resources.
为什么手动释放堆内存容易出错?
e.g:
Investment * createlnvestment();
void f () { Investment* plnv = createlnvestment();
……
delete plnv;
return;
}
//若……中有return导致提前返回,则会导致资源泄露,且不易察觉
std::auto_ptr
:已被弃用。由于其拷贝时原先的指针会指向null,致潜在的资源泄漏和行为不确定性。auto_ptr
被 C++11 中引入的 unique_ptr
所替代。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <memory> #include <iostream> class Test {public : Test () { std::cout << "Test Created\n" ; } ~Test () { std::cout << "Test Destroyed\n" ; } void greet () { std::cout << "Hello, World!\n" ; } }; int main () { { std::unique_ptr<Test> ptr1 (new Test()) ; ptr1->greet (); } std::unique_ptr<Test> ptr2 (new Test()) ; std::unique_ptr<Test> ptr3 = std::move (ptr2); if (!ptr2) { std::cout << "ptr2 is now empty.\n" ; } if (!ptr3) { std::cout << "ptr3 is now empty.\n" ; } ptr3->greet (); return 0 ; }
“引用计数智能指针”(Reference Counted Smart Pointer),即std::shared_ptr
,现已加入std标准库豪华套餐!环状引用问题
可用weak_ptr
解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <memory> class Test {public : Test () { std::cout << "Test Created\n" ; } ~Test () { std::cout << "Test Destroyed\n" ; } }; int main () { std::shared_ptr<Test> ptr1 = std::make_shared <Test>(); { std::shared_ptr<Test> ptr2 = ptr1; std::cout << "Inside block: ptr1 and ptr2 are both pointing to the object.\n" ; } std::cout << "Outside block: only ptr1 is pointing to the object.\n" ; return 0 ; }
请记住
防止资源泄漏,请使用RAII对象
,它们在构造函数中获得资源并在析构函数中释放资源。
两个常被使用的RAII classes
分别是std::shared _ptr
和unique_ptr
。前者通常是较佳选择,因为其copy 行为比较直观。
14. Think carefully about copying behavior in resource-managing classes 对于RAII
对象的复制,我们在大多数时候有一下两种可能:
禁止复制,如数据库连接具有唯一性
的特性,类中堆内存可能因执行多次delete
导致程序发生不明确行为。
对底层资源祭出“引用计数法”
若当引用次数为0
时,我们还想要做其他行为。那么则可以为shared_ptr
指定“删除器”
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> class Entity { public : void log () { std::cout << "Log Function" << std::endl; } Entity () { std::cout << "Constructor!" << std::endl; } ~Entity () { std::cout << "Destroctor!" << std::endl; } static void deleter (Entity* ptr) { std::cout << "Deleter!" << std::endl; delete ptr; } }; int main () { auto e = std::shared_ptr <Entity>(new Entity,Entity::deleter); e->log (); return 0 ; } Constructor! Log Function Deleter! Destroctor!
请记住
复制RAII 对象必须一并复制它所管理的资源,所以资源的copying 行为决定RAII 对象的copying 行为
普遍而常见的RAII class copying 行为是:抑制copying
、施行引用计数法(reference counting)
。不过其他行为也都可能被实现。
15. Provide access to raw resources in resource-managing classes //TODO
请记住
new
和delete
实际是由operator new
和operator delete
函数实现的。
如果你在new 表达式中使用[],必须在相应的delete 表达式中也使用[]。如果你在new 表达式中不使用[],一定不要在相应的delete 表达式中使用[]。
内存布局:
我们最好尽量不要对数组形式做typedef
或define
动作,而是采用vector
等templates
代替它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> typedef std::string Son[4 ]; int main () { std::string* Randolfluo = new Son; Randolfluo[0 ] = "123" ; Randolfluo[1 ] = "456" ; delete Randolfluo; return 0 ; } cpptools valgrind --leak-check=yes ./a.out User ==4172 == Invalid free () / delete / delete [] / realloc () ==4172 == at 0x484BB6F : operator delete (void *, unsigned long ) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==4172 == by 0x1092A7 : main (in /home/randolfluo/cpptools/a.out) ==4172 == Address 0x4dd6c88 is 8 bytes inside a block of size 136 alloc'd ==4172== at 0x484A2F3: operator new[](unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so) ==4172== by 0x109223: main (in /home/randolfluo/cpptools/a.out) // 在主函数,程序尝试释放的地址位于一个大小为136字节的块内,而这个块是通过调用operator new[]进行分配的。 //可以看到,程序发生了内存泄露,原因是new和delete的操作符不匹配 //使用vector: #include <iostream> #include<vector> typedef std::vector<std::string> Son; int main() { Son son(4); son.push_back("123"); son.push_back("456"); return 0; }
typedef
和define
的区别:
请记住
如果你在new 表达式中使用[],必须在相应的delete 表达式中也使用[]。如果你在new 表达式中不使用[],一定不要在相应的delete 表达式中使用[]。
17. Store newed objects in smart pointers in standalone statements
其实就是C++的编译器
可以对参数的执行顺序
进行改变——进行程序优化。Java
和C#
则会按照特定次序完成函数参数的核算。
1 2 3 4 5 6 7 8 9 10 11 12 13 int priority () ; void processWidget (std::shared_ptr<Widget> pw, int priority) ; processWidget (new Widget, priority ()); processWidget (std::shared_ptr <Widget>(new Widget), priority ()); std::shared_ptr<Widget> pw (new Widget) ; //在单独语句内以智能指针存储newed 所得对象。processWidge (pw, priority ()); //这个调用动作绝不至于造成泄漏。
以上之所以行得通,因为编译器对于“跨越语句的各项操作“没有重新排列的自由(只有在语句内它才拥有那个自由度)。
请记住
以独立语句将newed 对象存储千(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。