赞
踩
请支持原创~~
所谓智能指针,可以从字面上理解为“智能”的指针。具体来讲,智能指针和普通指针的用法是相似的,不同之处在于,智能指针可以在适当时机自动释放分配的内存。也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现。由此可见,C++ 也逐渐开始支持垃圾回收机制了,尽管目前支持程度还有限。
c++11 中发布了shared_ptr、unique_ptr、weak_ptr 用以资源的管理,都是定义在memory 这个头文件中。
#include <memory>
- constexpr shared_ptr() noexcept;
- template<class Y> explicit shared_ptr(Y* p);
- template<class Y, class D> shared_ptr(Y* p, D d);
- template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
- template <class D> shared_ptr(nullptr_t p, D d);
- template <class D, class A> shared_ptr(nullptr_t p, D d, A a);
- template<class Y> shared_ptr(const shared_ptr<Y>& r, T *p) noexcept;
- shared_ptr(const shared_ptr& r) noexcept;
- template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
- shared_ptr(shared_ptr&& r) noexcept;
- template<class Y> shared_ptr(shared_ptr<Y>&& r) noexcept;
- template<class Y> explicit shared_ptr(const weak_ptr<Y>& r);
- template<class Y> shared_ptr(auto_ptr<Y>&& r); // removed in C++17
- template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);
- shared_ptr(nullptr_t) : shared_ptr() { }
构造函数比较多啊,抽一个看看源码。
- template<class _Tp>
- inline
- shared_ptr<_Tp>::shared_ptr(shared_ptr&& __r) _NOEXCEPT
- : __ptr_(__r.__ptr_),
- __cntrl_(__r.__cntrl_)
- {
- __r.__ptr_ = 0;
- __r.__cntrl_ = 0;
- }
大概知道,shared_ptr 中存放一个对象的指针__ptr_ 和用以计数的__cntrl_,这两是shared_ptr 的私有成员变量:
- template<class _Tp>
- class shared_ptr {
- typedef _Tp element_type;
-
- private:
- element_type* __ptr_;
- __shared_weak_count* __cntrl_;
-
- ...
- }
另外,移动构造函数因为只是move,所以只是将旧的shared_ptr 转移到新的里。
- template<class _Tp>
- inline
- shared_ptr<_Tp>::shared_ptr(const shared_ptr& __r) _NOEXCEPT
- : __ptr_(__r.__ptr_),
- __cntrl_(__r.__cntrl_)
- {
- if (__cntrl_)
- __cntrl_->__add_shared();
- }
与移动构造相同,shared_ptr 实例,需要从参数中获得__ptr_ 和 __cntrl。
但是,与移动构造函数不同的是,拷贝构造时增加对象计数的。
下面举例shared_ptr 通常的创建方式:
- std::shared_ptr<int> p1; //不传入任何实参
- std::shared_ptr<int> p2(nullptr); //传入空指针 nullptr
- std::shared_ptr<int> p3(new int(10)); //指定指针为参数
- std::shared_ptr<int> p4(p3); //或者 std::shared_ptr<int> p4 = p3;
- std::shared_ptr<int> p5(std::move(p4)); //或者 std::shared_ptr<int> p5 = std::move(p4);
- shared_ptr& operator=(const shared_ptr& r) noexcept;
- template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;
- shared_ptr& operator=(shared_ptr&& r) noexcept;
- template<class Y> shared_ptr& operator=(shared_ptr<Y>&& r);
- template<class Y> shared_ptr& operator=(auto_ptr<Y>&& r); // removed in C++17
- template <class Y, class D> shared_ptr& operator=(unique_ptr<Y, D>&& r);
- void swap(shared_ptr& r) noexcept;
- void reset() noexcept;
- template<class Y> void reset(Y* p);
- template<class Y, class D> void reset(Y* p, D d);
- template<class Y, class D, class A> void reset(Y* p, D d, A a);
reset 基本上是对应构造
- T* get() const noexcept;
- T& operator*() const noexcept;
- T* operator->() const noexcept;
- long use_count() const noexcept;
- bool unique() const noexcept;
- explicit operator bool() const noexcept;
对于shared_ptr 的成员函数总结如下:
成员方法名 | 功 能 |
operator=() | 重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。 |
operator*() | 重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。 |
operator->() | 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。 |
swap() | 交换 2 个相同类型 shared_ptr 智能指针的内容。 |
reset() | 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。 |
get() | 获得 shared_ptr 对象内部包含的普通指针。 |
use_count() | 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。 |
unique() | 判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。 |
operator bool() | 判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true。 |
除了上面的成员函数外,C++11 标准还支持同一类型的 shared_ptr 对象,或者 shared_ptr 和 nullptr 之间,进行 ==,!=,,>= 运算。
注意:
- template<class T, class... Args>
- shared_ptr<T> make_shared(Args&&... args);
c++11 中针对shared_ptr 还提供了make_shared 这个外部函数,用以创建一个shared_ptr 实例。
c++ 建议尽可能使用make_shared 创建shared_ptr 实例。
下面来说明下make_shared 的优缺点。
shared_ptr
需要维护引用计数的信息:
如果你通过使用原始的 new 表达式分配对象, 然后传递给 shared_ptr (也就是使用 shared_ptr 的构造函数) 的话, shared_ptr 的实现没有办法选择, 而只能单独的分配控制块:
- auto p = new widget();
- shared_ptr sp1{ p }, sp2{ sp1 };
如果选择使用 make_shared
的话, 情况就会变成下面这样:
auto sp1 = make_shared(), sp2{ sp1 };
内存分配的动作, 可以一次性完成. 这减少了内存分配的次数, 而内存分配是代价很高的操作。
- void F(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs) { /* ... */ }
- F(std::shared_ptr<Lhs>(new Lhs("foo")),
- std::shared_ptr<Rhs>(new Rhs("bar")));
C++ 是不保证参数求值顺序, 以及内部表达式的求值顺序的, 所以可能的执行顺序如下:
好了, 现在我们假设在第 2 步的时候, 抛出了一个异常 (比如 out of memory, 总之, Rhs 的构造函数异常了), 那么第一步申请的 Lhs 对象内存泄露了. 这个问题的核心在于, shared_ptr 没有立即获得裸指针
我们可以用如下方式来修复这个问题.
- auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
- auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
- F(lhs, rhs);
当然, 推荐的做法是使用 std::make_shared
来代替:
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
make_shared
虽好, 但也存在一些问题, 比如, 当我想要创建的对象没有公有的构造函数时, make_shared
就无法使用了, 当然我们可以使用一些小技巧来解决这个问题, 比如这里 How do I call ::std::make_shared on a class with only protected or private constructors?
make_shared
只分配一次内存, 这看起来很好. 减少了内存分配的开销. 问题来了, weak_ptr
会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个 weak_ptr
离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 若引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题. 关于这个问题可以看这里 make_shared, almost a silver bullet
- #include <iostream>
- #include <memory>
- using namespace std;
-
- int main()
- {
- //构建 2 个智能指针
- std::shared_ptr<int> p1(new int(10));
- std::shared_ptr<int> p2(p1);
- //输出 p2 指向的数据
- cout << *p2 << endl;
- p1.reset();//引用计数减 1,p1为空指针
- if (p1) {
- cout << "p1 不为空" << endl;
- }
- else {
- cout << "p1 为空" << endl;
- }
- //以上操作,并不会影响 p2
- cout << *p2 << endl;
- //判断当前和 p2 同指向的智能指针有多少个
- cout << p2.use_count() << endl;
- return 0;
- }
运行结果:
- 10
- p1 为空
- 10
- 1
参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。