智能指针(一) unique_ptr

智能指针三个优点:

  • 明确资源的所有权
  • 避免忘记delete,造成内存泄露
  • 对于 exception 情况,执行内存释放(Effective C++ 中的条款)

智能指针类重载了解引用运算符(*)和成员指向运算符(->),同时为了能够在堆中管理各种类型,几乎所有的智能指针都是模板类,包含其功能的泛型实现。

C++11 中的auto_ptr已经废弃. 现有的 unique_ptrshared_ptrweak_ptr和原生指针加起来构成了指针的完整四件套。它们都在头文件<memory>里面,最常用的是原生指针(没所有权语义的时候),其次是unique_ptr,后两个除非特定场合需求,能不用就不用。 拷贝shared_ptreak_ptr都涉及到atomic操作,其开销比起拷贝、解引用一个指针都是大很多的。

unique_ptr

unique_ptr代表的是专属所有权,之所以叫这个名字,是因为它只能指向一个对象,即当它指向其他对象时,之前所指向的对象会被摧毁,不能进行复制操作只能进行移动操作。两个unique_ptr也不能指向一个对象. 看源码发现,拷贝构造函数和赋值运算符都加了delete:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
};

强行使用会报错:

这里需要注意: 既然不能拷贝, 就不能在函数中将unique_ptr作为参数了,因为传参是一个产生副本的过程,用 move(unique_ptr)取代

  • unique_ptr内部存储一个原生指针,当unique_ptr析构时,它的析构函数将会负责析构它持有的对象,还可以使用自定义的 删除器
  • unique_ptr提供了operator*()operator->()成员函数,像 raw pointer 一样,我们可以使用*解引用unique_ptr,使用->来访问unique_ptr所持有对象的成员。
  • unique_ptr并不提供 copy 操作,但提供了 move 操作,因此可以用std::move()来转移unique_ptr, 把一个unique_ptr的内存交给另外一个unique_ptr对象管理,。转移之后,当前对象不再持有此内存,新的对象将获得专属所有权
  • C++14 提供了std::make_unique<T>()函数用来直接创建unique_ptr,但 C++11 没有

unique_ptr和原生指针的大小是一样的,内存上没有任何的额外消耗,性能是最优的

如果没有为unique_ptr指定对象,get()返回0

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Test
{
public:
Test() { cout<< "construct" <<endl; }
~Test() { cout<< "destruct" <<endl; }
};

std::unique_ptr<Test> p1(new Test());
cout<< "p1: "<<p1.get()<<endl; // 0x19b7c20
// std::unique_ptr<Test> p2(p1) // 错误用法, 不支持拷贝构造函数
std::unique_ptr<Test> p2 = std::move(p1);
// 转移所有权到p2
cout<< "p1: "<<p1.get()<<endl; // 0
cout<< "p2: "<<p2.get()<<endl<<endl; // 0x19b7c20

// p2释放所有权, 只剩p3原生指针
// release并不会摧毁其指向的对象,不执行析构,与reset不同
Test* p3 = p2.release();
cout<< "after release "<<endl;
cout<< "p2: "<<p2.get()<<endl; // 0
cout<< "p3: "<<p3 <<endl<<endl; // 0x19b7c20

move不执行析构,否则新的智能指针无法指向对象了.

reset

reset有两种用法

  • 如果不加参数,就会销毁对象(执行析构函数),重置智能指针

  • 如果加原生指针做参数,就会先销毁原来指向的对象,然后指向原生指针指向的对象

    1
    2
    3
    4
    5
    6
    7
    8
    Test* p3 = new Test();
    std::unique_ptr<Test> p4(new Test());
    cout<<"p4: "<<p4.get()<<endl;

    p4.reset(p3);
    cout<< "after reset "<<endl;
    cout<< "p4: "<<p4.get()<<endl;
    cout<< "p3: "<<p3 <<endl;

运行结果:

1
2
3
4
5
6
7
8
construct   // p3
construct // p4
p4: 0x7adc50
destruct //
after reset
p4: 0x7acc20
p3: 0x7acc20
destruct

需要注意: release不执行析构, reset执行析构

自定义删除器

unique_ptr的定义删除器方式和shared_ptr不同,因为模板的参数不同,前者还需要指定删除器类型.

原型有:

  • std::unique_ptr up(t,d);
  • std::unique_ptr up(d); // 空的指针

T为指针管理的对象类型, D为删除器类型, t为管理的对象, d为删除器函数名称

1
2
3
4
5
6
7
void myclose(Test* t)
{
cout<< "close func"<<endl;
}

Test t;
std::unique_ptr<Test, decltype(myclose)*> p1(&t, myclose);

运行结果:

1
2
3
construct
close func
destruct

decltype用于获取myclose的类型, *表面它是一个指针类型,即函数指针.

make_unique 不是‘std’的成员 , 原因是make_unique为C++14才特有的, 如果使用gcc版本小于6.2,编译就会报错,vs2015 msvc 也可以

参考:
深入 C++ 的 unique_ptr
C++ 智能指针的正确使用方式