如果在程序中使用 new
从堆 (自由存储区) 分配内存,等到不再需要时,应使用delete
将其释放。C++
引入了智能指针auto ptr
,以帮助自动完成这个过程。随后的编程体验(尤其是使用STL
时)表明,需要有更精致的机制。基于程序员的编程体验和BOOST
库提供的解决方案,C++11
摒弃了 auto_pt
并新增了三种智能指针 unique_ptr
、shared_ptr
和 weak_ptr
,所有新增的智能指针都能与STL容器
和移动语义
协同工作
1. 为何使用智能指针
在程序中使用 new
从堆 (自由存储区) 分配内存,等到不再需要时,应使用delete
将其释放。但是即使在代码中使用delete
将其释放内存,也可能造成问题,如以下代码,当出现异常时,delete
将不被执行而导致内存泄漏。
void remodel(std::string &str) {
std::string *ps = new std::string(str);
...
if (weird_thing())
throw exception();
str = *ps;
delete ps;
return;
}
当remodel()
这样的函数终止(不管是正常终止,还是由于出现了异常而终止),本地变量都将从栈内存中删除——因此指针 ps
占据的内存将被释放。如果 ps
指向的内存也被释放,那该有多好啊。如果ps
有一个析构函数,该析构函数将在 ps
过期时释放它指向的内存。因此,ps
的问题在于,它只是一个常规指针,不是有析构函数的类对象。如果它是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是auto_ptr
、unique_ptr
和 shared_ptr
背后的思想。模板auto_ptr
是C++98
提供的解决方案,C++11
已将其摒弃,并提供了另外两种解决方案。然而,虽然 auto_ptr
被摒弃,但它已使用了多年;同时,如果您的编译器不支持其他两种解决方案,auto_ptr
将是唯一的选择。
2. 使用智能指针
这三个智能指针模板(auto_ptr
、unique_ptr
和 shared_ptr
)都定义了类似指针的对象,可以将new
获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete
来释放内存。因此,如果将new
返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动被释放。图16.2说明了auto_ptr
和常规指针在行为方面的差别;share_ptr
和 unique_ptr
的行为与auto_ptr
相同。
Smart Pointer Considerations
Begin by considering assignment:
auto_ptr<string> ps (new string("I reigned lonely as a cloud."));
auto_ptr<string> vocation;
vocation = ps;
What should the assignment statement accomplish? If ps
and vocation
were ordinary pointers, the result would be two pointers pointing to the same string object. That is not acceptable here because the program would wind up attempting to delete the same object twice—once when ps
expires,and once when vocation
expires. There are ways to avoid this problem:
-
Define the assignment operator so that it makes a deep copy. This results in two pointers pointing to two distinct objects, one of which is a copy of the other.
-
Institute the concept of ownership, with only one smart pointer allowed to own a particular object. Only if the smart pointer owns the object will its destructor delete the object. Then have assignment transfer ownership. This is the strategy used for
auto_ptr
and forunique_ptr
, althoughunique_ptr
is somewhat more restrictive. -
**Create an even smarter pointer that keeps track of how many smart pointers refer to a particular object. **This is called
reference counting
. Assignment, for example, would increase the count by one,and the expiration of a pointer would decrease the count by one. Only when the final pointer expires would delete be invoked. This is theshared_ptr
strategy.
The same strategies we’ve discussed for assignment, of course, would also apply to the copy constructors.
Each approach has its uses. Listing 16.6 shows an example for which auto_ptr
is poorly suited.
// fowl.cpp -- auto_ptr a poor choice
#include <iostream>
#include <string>
#include <memory>
int main() {
using namespace std;
auto_ptr<string> films[5] = {
auto_ptr<string>(new string("Fowl Balls")),
auto_ptr<string>(new string("Duck Walks")),
auto_ptr<string>(new string("Chicken Runs")),
auto_ptr<string>(new string("Turkey Errors")),
auto_ptr<string>(new string("Goose Eggs"))
};
auto_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership
cout << "The nominees for best avian baseball film are\n";
for (int i = 0; i < 5; i++)
cout << *films[i] << endl;
cout << "The winner is " << *pwin << "!\n";
cin.get();
return 0;
}
Here is some sample output:
The nominees for best avian baseball film are
Fowl Balls
Duck Walks
Segmentation fault (core dumped)
The “core dumped” message should help fix in your memory that a misused auto_ptr
can be a problem. (The behavior for this sort of code is undefined, so you might encounter different behavior, depending on your system.) Here the problem is that the following statement transfers ownership from films[2]
to pwin
:
pwin = films[2]; // films[2] loses ownership
That causes films[2]
to no longer refer to the string. After an auto_ptr
gives up ownership of an object, it no longer provides access to the object. When the program goes to print the string pointed to by films[2]
, it finds the null pointer, which apparently is an unpleasant surprise.
Suppose you go back to Listing 16.6 but use shared_ptr
instead of auto_ptr
. (You’ll need a compiler that supports the C++11
shared_ptr
class.) Then the program runs fine and gives this output:
The nominees for best avian baseball film are
Fowl Balls
Duck Walks
Chicken Runs
Turkey Errors
Goose Eggs
The winner is Chicken Runs!
2.1 Why unique_ptr
Is Better than auto_ptr
auto_ptr<string> p1(new string("auto"); //#1
auto_ptr<string> p2; //#2
p2 = p1; //#3
在语句#3
中,p2
接管 string
对象的所有权后,p1
的所有权将被剥夺。前面说过,这是件好事,可防止p1
和p2
的析构函数试图删除同一个对象;但如果程序随后试图使用p1
,这将是件坏事,因为p1
不再指向有效的数据。
unique_ptr<string> p3(new string("auto"); //#4
unique_ptr<string> p4; //#5
p4 = p3; //#6
编译器认为语句#6
非法,避免了p3
不再指向有效数据的问题。因此,unique_ptr
比 auto_ptr
更安全(编译阶段错误比潜在的程序崩溃更安全)。
但有时候,将一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下函数定义:
unique_ptr <string> demo(const char *s) {
unique_ptr <string> temp(new string(s));
return temp;
}
unique_ptr<string> ps;
ps = demo("Uniquely special");
demo()
返回一个临时unique_ptr
,然后 ps
接管了原本归返回的unique_ptr
所有的对象,而返回的unique_ptr
被销毁。这没有问题,因为ps
拥有了 string
对象的所有权。但这里的另一个好处是,demo()
返回的临时unique_ptr
很快被销毁,没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。神奇的是,编译器确实允许这种赋值!
总之,程序试图将一个unique_ptr
赋给另一个时,如果源unique_ ptr
是个临时右值,编译器允许这样做;如果源unique_ptr
将存在一段时间,编译器将禁止这样做:
using namespace std;
unique_ptr<string> pu1(new string "Hi ho!");
unique_ptr<string> pu2;
pu2 = pu1; // #1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string "Yo!"); // #2 allowed
相比于auto_ptr
,unique_ptr
还有另一个优点。它有一个可用于数组的变体。别忘了,必须将delete
和new
配对,将delete []
和new []
配对。模板auto_ptr
使用delete
而不是delete []
,因此只能与new
一起使用,而不能与new []
一起使用。但unique_ptr
有使用new[]
和 delete []
的版本:
std::unique_ptr< double[]>pda(new double(5)); // will use delete []
警告:使用new
分配内存时,才能使用auto_ ptr
和 shared_ptr
,使用new []
分配内存时,不能使用它们。不使用new
分配内存时,不能使用auto_ptr
或shared_ptr
;不使用new
或new[]
分配内存时,不能使用unique_ptr
。
2.2 选择智能指针
应使用哪种智能指针呢?如果程序要使用多个指向同一个对象的指针,应选择shared_ptr
。这样的情况包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;两个对象包含都指向第三个对象的指针;STL
容器包含指针。很多STL
算法都支持复制和赋值操作,这些操作可用于shared_ptr
,但不能用于unique ptr
(编译器发出警告)和 auto_ptr
(行为不确定)。如果您的编译器没有提供shared_ptr
,可使用 Boost
库提供的shared_ptr
。
如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr
。如果函数使用new
分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr
是不错的选择。这样,所有权将转让给接受返回值的unique_ptr
, 而该智能指针将负责调用delete
。可将unique_ptr
存储到 STL
容器中,只要不调用将一个 unique_ptr
复制或赋给另一个的方法或算法(如sort()
)。
3. shared_ptr
使用示例
// shared_ptr constructor example
#include <iostream>
#include <memory>
struct C {
int *data;
};
int main() {
std::shared_ptr<int> p1;
std::shared_ptr<int> p2(nullptr);
std::shared_ptr<int> p3(new int);
std::shared_ptr<int> p4(new int, std::default_delete<int>());
std::shared_ptr<int> p5(new int, [](int *p) { delete p; }, std::allocator<int>());
std::shared_ptr<int> p6(p5);
std::shared_ptr<int> p7(std::move(p6));
std::shared_ptr<int> p8(std::unique_ptr<int>(new int));
std::shared_ptr<C> obj(new C);
std::shared_ptr<int> p9(obj, obj->data);
std::cout << "use_count:\n";
std::cout << "p1: " << p1.use_count() << '\n';
std::cout << "p2: " << p2.use_count() << '\n';
std::cout << "p3: " << p3.use_count() << '\n';
std::cout << "p4: " << p4.use_count() << '\n';
std::cout << "p5: " << p5.use_count() << '\n';
std::cout << "p6: " << p6.use_count() << '\n';
std::cout << "p7: " << p7.use_count() << '\n';
std::cout << "p8: " << p8.use_count() << '\n';
std::cout << "p9: " << p9.use_count() << '\n';
return 0;
}
use_count:
p1: 0
p2: 0
p3: 1
p4: 1
p5: 2
p6: 0
p7: 2
p8: 1
p9: 2
参考资料:
[1] C++.Primer.Plus.6th.Edition.
[2] C++ Reference