就像前面讨论到的,C++中的内存管理是错误与bug的罪恶之源。这许多的bug由于使用动态内存与指针遭遇不断上升。当你在程序中大量使用动态内存分配,在对象之间传递许多指针时,就很难记住对每一个指针只在正确的时间进行一次delete调用。弄错了的后果是很严重的:当你将动态分配的内存释放了多次,或者使用指向已经释放了内存的指针时,就会造成内存崩溃或者严重的运行时错误;而当你忘记对动态分配的内存进行释放时,又会造成内存渗露。
智能指针会帮助你管理动态分配内存,是推荐的避免内存渗露的技巧。从概念上来说,智能指针可以吼住像内存这样的动态分配资源。当智能指针不在范围内或者被重置时,就会自动释放吼住的资源。智能指针可以被用于在函数范围内,或者在类的成员函数范围内管理动态分配的资源。也可以用于在函数参数中传递动态分配资源的属主。
C++提供了山积 使动态指针吸引人的几个语言属性。首先,你可以写一个使用模板的任何指针类型的类型安全的智能指针类。其次,可以提供一个智能指针的接口,使用重载操作符,允许 代码使用智能指针对象,就像原始的傻瓜指针一样。需要特别指出的是,可以重载*,->,以及[]操作符,客户端代码可以像间接引用正常指针一样对智能指针进行同样的操作。
有好几种类型的智能指针。最简单的类型就是取得了资源的主要/独一无二的所有权。作为资源的唯一属主,智能指针可以在指针不在活动范围或者被重置时自动释放所指向的资源 。标准库提供了std::unique_ptr,这是一个带有唯一属主语法的智能指针。
更高级一点的智能指针允许有共享属主;也就说,几个智能指针指向同一个资源。当这样的一个智能指针不在活动范围或者被重置时,那就会在只有它是指向该资源的最后一个智能指针时才会释放指向的资源。标准库提供了std::shared_ptr支持共享属主。
unique_ptr与shared_ptr两个标准智能指针,都定义在了<memory>中,我们会进行详细讨论。
缺省情况下要用unique_ptr。只有在需要共享资源时才会乃至shared_ptr。
千万不要将资源分配的结果赋值会原始指针!不管你用什么样的资源分配方法,都要立刻马上把资源指针放到智能指针当中,不管是unique_ptr还是shared_ptr,或者是使用RAII(资源获取即初始化)类。RAII类对特定资源取得所有权,在适当的时间处理释放问题。这是一个设计技巧,我们以后再讨论。
1、unique_ptr
unique_ptr对资源拥有独一无二的所有权。当unique_ptr被破坏或者被重置时,资源会自动释放。一个优点是内存与资源总是会被释放,即使是return语句被执行或者抛出例外。这样的话,举个例子,当一个函数拥有多个return语句时就会简化编码,因为不必要记住在每个return语句前面释放资源了。
作为一个经验法则,在unique_ptr实例中总要把动态分配的资源归属到一个单独的属主中。
1.1、生成unique_ptr
考虑下面的函数,它公然地通过在自由内存空间上分配一个Simple对象而没有释放它来制造了一个内存渗露:
void leaky()
{
Simple* mySimplePtr{ new Simple{} }; //BUG! Memory is never released!
mySimplePtr->go();
}
有时候你可能会想你的代码已经正确地释放了动态分配的内存。但不幸的是,极大可能是并不是在所有情况下都是正确的。看一下下面的函数:
void couldBeLeaky()
{
Simple* mySimplePtr{ new Simple{} };
mySimplePtr->go();
delete mySimplePtr;
}
这个函数动态分配了一个Simple对象,使用了这个对象,然后正确调用了delete。然而,在这个例子中仍然可能会有内存渗露!如果go()成员函数抛出例外,对delete的调用永远不会被执行,造成内存渗露。
那怎么办呢?你应该使用unique_ptr,用std::make_unique()类的协助函数来生成。unique_ptr是一个通用智能指针,可以指向任何类型的内存。这就是为什么它是一个类模板,而make_unique()是一个函数模板。两者都要求中括号之间的模板参数,<>,指定了要将unique_ptr指向的内存类型。模板会在以后的博文中讨论,其细节对于理解如何使用智能指针并不重要。
下面的函数使用了unique_ptr而不是原始指针。Simple对象并没有显式地被删除;但当unique_ptr实例不在活动范围(函数的结尾,或者抛出例外),它就会调用析构函数自动释放Simple对象。
void notLeaky()
{
auto mySimpleSmartPtr{ make_unique<Simple>() };
mySimpleSmartPtr->go();
}
这段代码使用了make_unique(),与auto关键字相结合,这样的话只需要指定指针的类型一次,在这个例子中,就是Simple。这是生成unique_ptr的推荐方式。如果Simple构造函数需要参数,要把它们作为参数传递给make_unique()。