智能指针

智能指针

简介

  • 智能指针(smart pointer)是C++用来自动管理对象生命周期的模板类;

  • 头文件 #include <memory>

unique_ptr

  • 独占指针,对象所有权只能被一个指针所拥有,无法被两个或两个以上同时拥有;

  • 不能进行拷贝及赋值操作,只能直接初始化;

  • 可以从函数中返回一个unique_ptr

  • unique_ptr为数组提供了模板偏特化,因此unique_ptr也可以指向数组;

  • c++14 提供了make_unique来直接创建;

  • 作为函数参数使用时,使用引用可避免所有权转移;

  • 实现原理:

    • 指针成员私有;

    • 禁用复制构造函数赋值函数

定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//定义
namespace std {
    template <typename T, typename D = default_delete<T>>
    class unique_ptr
    {
    public:
        explicit unique_ptr(pointer p) noexcept;
        ~unique_ptr() noexcept;    
        T& operator*() const;
        T* operator->() const noexcept;
        unique_ptr(const unique_ptr &) = delete;                //禁用复制构造函数
        unique_ptr& operator=(const unique_ptr &) = delete;     //禁用赋值函数
        unique_ptr(unique_ptr &&) noexcept;
        unique_ptr& operator=(unique_ptr &&) noexcept;
    // ...
    private:
        pointer __ptr;
    };
}
//c++14
template <typename T, typename... Ts>
std::unique_ptr<T> make_unique( Ts&&... params ) {
    return std::unique_ptr<T>( new T( std::forward<Ts>(params)... ) );
}

用法

 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
// 4种初始化方式
unique_ptr<int> up1(new int());                    // 使用原始指针初始化
unique_ptr<int> up2;          
up2.reset(up1.release());                          // 使用reset初始化
unique_ptr<int> up3 = std::move(up2);              // 通过move转移所有权
auto ptr = make_unique<std::string>("senlin");     // c++14使用make_unique

//容器中使用
vector<unique_ptr<int>> vec;
unique_ptr<int> p(new int(5));
vec.push_back(std::move(p));

//函数参数及返回
void fun1(unique_ptr<int> arg);
fun1(std::move(p));   //必须使用move将所有权转移

//使用引用,无需转移所有权
void fun2(unique_ptr<int> &arg);
fun2(p);   

//函数返回值为右值,无需使用move转移所有权
unique_ptr<int> func3() {
  return p; 
}

//unique_ptr管理数组
unique_ptr<int[]> p(new int[5] {1, 2, 3, 4, 5});

shared_ptr

  • 共享指针,对象所有权可以被多个共享指针同时拥有;

  • 内部使用引用计数自动记录引用次数的多少,当引用次数为0时,对象自动销毁;

  • 使用make_shared创建;

  • 可以使用一个new表达式返回的指针进行初始化;但是不能将一个new表达式返回的指针赋值给shared_ptr;

  • 一旦将一个指针交由shared_ptr管理之后,就不要再通过普通指针访问这块内存;

  • 可以通过reset方法重置指向另一个对象,此时原对象的引用计数减一;

  • 可以定制一个deleter函数,用于在shared_ptr释放对象时调用;

  • 在有通过this指针构建shared_ptr的情况下要继承std::enable_shared_from_this

  • shared_ptr的计数操作具有原子性, 多线程操作不同的shared_ptr进行操作是线程安全的;

  • shared_ptr本身就没有保证线程安全,多线程同时访问同一个shared_ptr对象线程不安全;

1
2
3
4
5
6
7
cout<<"test shared_ptr and new:"<<endl;
shared_ptr<int> p4(new int(1024));
//shared_ptr<int> p5 = new int(1024); // wrong, no implicit constructor
cout<<*p4<<endl;

auto p2 = make_shared<string>("world");
cout<<*p1<<' '<<*p2<<endl;

enable_shared_from_this

如果涉及到将this指针提升为shared_ptr的情况,直接提升会新建一个manager object。

使用两个manager object管理同一个对象会造成不可预知的后果。为避免这种情况,需要在对象中维护一个weak_ptr。这是通过enable_shared_from_this自动完成的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void f(shared_ptr<Thing>);
class Thing {
public:
    void foo() {
        //f(shared_ptr<Thing>(this));   //new manager object A
        f(shared_from_this());  //use manager object B
    }
};
int main() {
    shared_ptr<Thing> sp(new Thing());  //new manager object B
    sp->foo();
}

当需要在object内部使用this指针时,调用shared_from_this()就可以避免新建manager object。需要注意的是,在构造函数中,对象还未构造完毕,并没有交由shared_ptr管理,即manager object还未创建,所以不能使用shared_from_this。

weak_ptr

  • weak_ptr一般和shared_ptr配合使用,用于消除shared_ptr循环引用问题;

  • 指向shared_ptr所指向的对象,但是却不增加对象的引用计数;

  • weak_ptr有一个lock函数,尝试取回一个指向对象的shared_ptr;

1
2
3
4
5
6
auto p10 = make_shared<int>(1024);
weak_ptr<int> wp1(p10);
cout<<"p10 use_count: "<<p10.use_count()<<endl;
//p10.reset(new int(1025)); // this will cause wp1.lock() return a false obj
shared_ptr<int> p11 = wp1.lock();
if(p11) cout<<"wp1: "<<*p11<<" use count: "<<p11.use_count()<<endl;

auto_ptr

  • c++98中使用,c++17中移除,不建议使用;

最佳实践

  • 必须保证所有managed object只有一个manager object;

  • 能用裸指针解决问题的情况下,就不要使用智能指针;

  • 如果决定了用智能指针,那就不要用裸指针管理同一个对象;

  • 能用unique_ptr管理的对象,不要使用shared_ptr/weak_ptr

参考

  1. 深入 C++ 的 unique_ptr | Senlin's Blog

  2. https://zhuanlan.zhihu.com/p/30933682?utm_source=wechat_session&utm_medium=social&utm_oi=28398072102912

updatedupdated2024-05-102024-05-10