内存问题一直是 C/C++ 程序员面临的重大挑战, 就语言层面而言, 主要问题有:
野指针: 一些内存单元已被释放, 之前指向它的指针却还在被使用这些内存有可能被系统重新分配给程序使用, 从而导致了无法预测的错误
重复释放: 程序试图去释放已经被释放过的内存单元, 或者释放已经被重新分配过的内存单元, 导致重复释放错误
内存泄漏: 不再需要使用的内存单元如果没有被释放就会导致内存泄漏如果程序不断地重复进行这类操作, 将会导致内存占用剧增, 甚至导致系统无可用内存, 严重影响系统的运行
随着多线程程序的出现和广泛使用, 内存问题更加突出为了让程序员从内存管理细节中解放出来, 越来越多的程序及程序库采用了智能指针(smart pointer)
智能指针
智能指针泛指一类原生指针 (raw pointer) 的封装, 它的行为非常类似于原生指针, 同时又可以自动化地实现资源管理 (比如对象的自动析构) 智能指针使用广泛, 几乎在所有的大型工程中都可以看到它的身影, 比如 boostAndroidwebKitChromium 中都自行设计了智能指针虽然这些开源项目中智能指针的实现各有不同, 但其基本原理都是一样的:
在 C++ 语言中, 智能指针对象作为栈上分配的自动变量存在, 比如局部变量类的成员变量, 在代码执行上下文退出其作用域时被自动析构
智能指针的析构函数中一般包含封装指针对象的 delete 操作, 从而间接实现了被封装对象的自动析构
下面的代码展示智能指针的一般实现:
- template <typename T>
- class SmartPtr {
- public:
- typedef T ValueType;
- typedef ValueType *PtrType;
- SmartPtr() : m_ptr(NULL) {}
- SmartPtr(PtrType ptr) : m_ptr(ptr) {}
- ~SmartPtr() { if (m_ptr) delete m_ptr; }
- SmartPtr(const SmartPtr<T>& o);
- template<typename U> SmartPtr(const SmartPtr<U>& o);
- template<typename U> SmartPtr& operator=(const SmartPtr<U>& o);
- // 指针运算
- ValueType& operator*() const { return *m_ptr; }
- PtrType operator->() const { return m_ptr; }
- // 逻辑运算符重载
- bool operator!() const { return !m_ptr; }
- // 转换为 raw ptr
- operator PtrType() { return m_ptr; }
- private:
- PtrType m_ptr;
- };
- auto_ptr
在 C++ 98 中, 智能指针通过一个模板类型 auto_ptr 来实现, 程序员只需将 new 操作返回的指针作为 auto_ptr 的初始值即可, 不用再显式的调用 delete 比如:
auto_ptr(new int);
这在一定程度上避免了堆内存忘记释放而造成的内存泄漏但是 auto_ptr 存在很明显的缺点, 它采取了独占所有权模式, 这样两个相同类型的指针不能同时指向相同的资源比如下面的代码:
- // C++ program to illustrate the use of auto_ptr
- #include<iostream>
- #include<memory>
- using namespace std;
- class A
- {
- public:
- void show() { cout << "A::show()" << endl; }
- };
- int main()
- {
- // p1 is an auto_ptr of type A
- auto_ptr<A> p1(new A);
- p1 -> show();
- // returns the memory address of p1
- cout << p1.get() << endl;
- // copy constructor called, this makes p1 empty.
- auto_ptr <A> p2(p1);
- p2 -> show();
- // p1 is empty now
- cout << p1.get() << endl;
- // p1 gets copied in p2
- cout<< p2.get() << endl;
- return 0;
- }
输出为:
- A::show()
- 0x1b42c20
- A::show()
- 0
- 0x1b42c20
形象的用图形表示如下:
auto_ptr 的拷贝构造函数和赋值操作符实际上并不复制已存储的指针, 而是传递它, 使第一个 auto_ptr 对象为空由于 auto_ptr 不支持拷贝语义, 所以不能用于 STL 容器, 它的另一个缺点就是不能调用 delete[], 而无法用于数组
介于 auto_ptr 有上述的缺点, 所以在 C++11 标准中被废弃了, 取而代之的是 unique_ptr, shared_ptr 及 weak_ptr
unique_ptr
C++11 中 unique_ptr 是用来取代 auto_ptr 的, 就像其名字所表明的, 它与所指对象的内存紧密绑定, 不能与其他 unique_ptr 类型的指针对象共享所指对象的内存比如下面的代码是无法编译通过的:
- unique_ptr<int> up1(new int(11));
- unique_ptr<int> up2 = up1; // 不能通过编译
相比 auto_ptr, 这可以避免程序中误用拷贝当然, 如果程序员确实希望使用 auto_ptr 那样的转移所有权操作, 可以借助 std::move 来完成:
- // Works, resource now stored in ptr2
- unique_ptr < A > ptr2 = move(ptr1);
值得注意的是, 如果 unique_ptr 作为函数的返回值, 下面的代码会自动使用 move 语义而不会出现编译错误:
- unique_ptr<A> fun()
- {
- unique_ptr<A> ptr(new A);
- /* ...
- ... */
- return ptr;
- }
此外 unique_ptr 还增加了对数组的支持, 所以在代码中应该使用 unique_ptr 而不应该使用废弃了的 auto_ptr
shared_ptr
在有的情形下, 程序可能需要共享拥有同一个堆分配对象的内存, 这个时候 shared_ptr 就可以派上用场 shared_ptr 采用引用计数所有权模型, 它与 shared_ptr 的所有副本合作维护其包含的指针的引用计数每当一个新的指针指向资源时, 计数器就会增加, 当 shared_ptr 析构时, 计数器就会递减引用计数大于零时, shared_ptr 包含的原始指针不会被销毁, 直到引用计数递减到零才会释放
所以, 当我们要分配一个原始指针给多个所有者时, 应该使用 shared_ptr
- // C++ program to demonstrate shared_ptr
- #include<iostream>
- #include<memory>
- using namespace std;
- class A
- {
- public:
- void show()
- {
- cout<<"A::show()"<<endl;
- }
- };
- int main()
- {
- shared_ptr<A> p1 (new A);
- cout << p1.get() << endl;
- p1->show();
- shared_ptr<A> p2 (p1);
- p2->show();
- cout << p1.get() << endl;
- cout << p2.get() << endl;
- // Returns the number of shared_ptr objects
- //referring to the same managed object.
- cout << p1.use_count() << endl;
- cout << p2.use_count() << endl;
- // Relinquishes ownership of p1 on the object
- //and pointer becomes NULL
- p1.reset();
- cout << p1.get() << endl;
- cout << p2.use_count() << endl;
- cout << p2.get() << endl;
- return 0;
- }
输出:
- 0x1c41c20
- A::show()
- A::show()
- 0x1c41c20
- 0x1c41c20
- 2
- 2
- 0 // NULL
- 1
- 0x1c41c20
- weak_ptr
在 C++11 标准中, 除了 unique_ptr 和 shared_ptr, 智能指针还包括了 weak_ptr 这个类模板 weak_ptr 的使用更为复杂一点, 它可以指向 shared_ptr 指针指向的对象内存, 却并不拥有该内存使用 weak_ptr 成员函数 lock, 则可返回其指向内存的一个 shared_ptr 对象, 且在所指对象内存已经无效时, 返回空指针
为什么会引入 weak_ptr 呢? 它是为了解决 shared_ptr 循环依赖导致的内存问题而引入的让我们考虑一个场景, 有两个类 A 和 B, 都包含指向对方类的 shared_ptr 指针, 这样 A 指向 B,B 指向 A, 引用计数永远不会达到零, 两个对象也永远不会被删除
引入 weak_ptr 后, 声明 weak_ptr 的类不共享所有权, 但是它可以通过 lock 方法访问这些对象通过 weak_ptr, 可以打破 A 和 B 之间的循环依赖
总结
虽然智能指针能帮助用户进行有效的堆内存管理, 但是它还是需要程序员显式地声明智能指针此外我们需要小心地使用 shared_ptr, 避免循环依赖导致内存无法释放 weak_ptr 提供了解决循环依赖的途径, 但决定何时使用 shared_ptr, 何时使用 weak_ptr 仍然是程序员的职责
参考
深入理解 C++11: C++11 新特性解析与应用, p163 ~ p173
深入理解 Android: WebKit 卷, p40 ~ p45
auto_ptr, unique_ptr, shared_ptr and weak_ptr
来源: http://blog.csdn.net/mogoweb/article/details/79286117