智能指针unique_ptr auto_ptr scoped_ptr

unique_ptr

unique_ptr标准库源码

查看C++标准库源代码bits/目录下能找到一个名为unique_ptr.h的文件,包含了unique_ptr的实现

unique_ptr的部分源码如下:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
template <typename _Tp, typename _Dp = default_delete<_Tp>>
class unique_ptr
{
template <typename _Up>
using _DeleterConstraint =
typename __uniq_ptr_impl<_Tp, _Up>::_DeleterConstraint::type;

__uniq_ptr_data<_Tp, _Dp> _M_t;

public:
using pointer = typename __uniq_ptr_impl<_Tp, _Dp>::pointer;
using element_type = _Tp;
using deleter_type = _Dp;

public:
// 右值引用的拷贝构造函数
unique_ptr(unique_ptr<_Up, _Ep>&& __u) noexcept
: _M_t(__u.release(), std::forward<_Ep>(__u.get_deleter()))
{ }
// 右值引用的operator=赋值重载函数
unique_ptr&>::type
operator=(unique_ptr<_Up, _Ep>&& __u) noexcept
{
reset(__u.release());
get_deleter() = std::forward<_Ep>(__u.get_deleter());
return *this;
}

// Disable copy from lvalue.
/* 删除了unique_ptr的拷贝构造和operator=赋值函数,
因此不能做unique_ptr智能指针对象的拷贝构造和
赋值,防止浅拷贝的发生 */
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

/// Destructor, invokes the deleter if the stored pointer is not null.
// 析构函数中通过自定义删除器释放资源
~unique_ptr()
{
auto& __ptr = _M_t._M_ptr();
if (__ptr != nullptr)
get_deleter()(__ptr);
__ptr = pointer();
}

/// Return the stored pointer.
// unique_ptr提供->运算符的重载函数
pointer operator->() const noexcept
{
_GLIBCXX_DEBUG_PEDASSERT(get() != pointer());
return get();
}

/// Return the stored pointer.
// 返回智能指针对象底层管理的指针
pointer get() const noexcept
{ return _M_t._M_ptr(); }

/// Return @c true if the stored pointer is not null.
/* 提供bool类型的重载,使unique_ptr对象可以
直接使用在逻辑语句当中,比如if,while等 */
explicit operator bool() const noexcept
{ return get() == pointer() ? false : true; }

// 功能和auto_ptr的release函数功能相同,最终只有一个unique_ptr指针指向资源
pointer release() noexcept
{
pointer __p = _M_ptr();
_M_ptr() = nullptr;
return __p;
}
// 把unique_ptr原来的旧资源释放,重置为新的资源__p
void reset(pointer __p) noexcept
{
const pointer __old_p = _M_ptr();
_M_ptr() = __p;
if (__old_p)
_M_deleter()(__old_p);
}

}

unique_ptr构造

1
2
3
4
// 示例
unique_ptr<int> ptr(new int);
unique_ptr<int> ptr2 = std::move(ptr); // 使用了右值引用的拷贝构造
ptr2 = std::move(ptr); // 使用了右值引用的operator=赋值重载函数

参考:std::unique_ptr::unique_ptr - cppreference.com

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>
#include <memory>

struct Foo // object to manage
{
Foo() { std::cout << "Foo ctor\n"; }
Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }
Foo(Foo&&) { std::cout << "Foo move ctor\n"; }
~Foo() { std::cout << "~Foo dtor\n"; }
};

struct D // deleter
{
D() {};
D(const D&) { std::cout << "D copy ctor\n"; }
D(D&) { std::cout << "D non-const copy ctor\n"; }
D(D&&) { std::cout << "D move ctor \n"; }
void operator()(Foo* p) const
{
std::cout << "D is deleting a Foo\n";
delete p;
};
};

int main()
{
std::cout << "Example constructor(1)...\n";
std::unique_ptr<Foo> up1; // up1 is empty
std::unique_ptr<Foo> up1b(nullptr); // up1b is empty

std::cout << "Example constructor(2)...\n";
{
std::unique_ptr<Foo> up2(new Foo); //up2 now owns a Foo
} // Foo deleted

std::cout << "Example constructor(3)...\n";
D d;
{ // deleter type is not a reference
// D重载了运算符(),并且参数为Foo指针,函数对象d作为自定义删除器
std::unique_ptr<Foo, D> up3(new Foo, d); // deleter copied
}
{ // deleter type is a reference
std::unique_ptr<Foo, D&> up3b(new Foo, d); // up3b holds a reference to d
}

std::cout << "Example constructor(4)...\n";
{ // deleter is not a reference
std::unique_ptr<Foo, D> up4(new Foo, D()); // deleter moved
}

std::cout << "Example constructor(5)...\n";
{
std::unique_ptr<Foo> up5a(new Foo);
// 带右值引用参数的拷贝构造
std::unique_ptr<Foo> up5b(std::move(up5a)); // ownership transfer
}

std::cout << "Example constructor(6)...\n";
{
std::unique_ptr<Foo, D> up6a(new Foo, d); // D is copied
std::unique_ptr<Foo, D> up6b(std::move(up6a)); // D is moved

std::unique_ptr<Foo, D&> up6c(new Foo, d); // D is a reference
std::unique_ptr<Foo, D> up6d(std::move(up6c)); // D is copied
}

Effective Modern C++条款21:尽量使用std::make_unique而不直接使用new

1.避免在将new指针赋值智能指针前,程序发生异常,导致赋值失败,则刚刚new出来的指针将无法得到正常释放,从而内存泄漏。

2.使用make_shared只会执行一次内存分配,即将对象及控制块同时分配到一块内存上

但是std::make_unique不允许使用自定义析构器

unique_ptr::get()

通过源码,get函数返回unique_ptr对象底层管理的指针

1
2
3
// 返回智能指针对象底层管理的指针
pointer get() const noexcept
{ return _M_t._M_ptr(); }

unique_ptr::release()

依然从源码来看,release函数返回了unique_ptr对象底层管理的指针,并且将unique_ptr对象底层管理的指针置空

1
2
3
4
5
6
7
// 功能和auto_ptr的release函数功能相同,最终只有一个unique_ptr指针指向资源
pointer release() noexcept
{
pointer __p = _M_ptr();
_M_ptr() = nullptr;
return __p;
}

unique_ptr::reset()

从源码来看,reset函数将unique_ptr对象底层管理的指针更换为新的指针,并调用自定义删除器将旧指针指向的资源释放

1
2
3
4
5
6
7
8
// 把unique_ptr原来的旧资源释放,重置为新的资源__p
void reset(pointer __p) noexcept
{
const pointer __old_p = _M_ptr();
_M_ptr() = __p;
if (__old_p)
_M_deleter()(__old_p);
}

unique_ptr::swap()

swap交换资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// unique_ptr::swap example
#include <iostream>
#include <memory>

int main () {
std::unique_ptr<int> foo (new int(10));
std::unique_ptr<int> bar (new int(20));

foo.swap(bar);

std::cout << "foo: " << *foo << '\n';
std::cout << "bar: " << *bar << '\n';

//输出结果:
//foo: 20
//bar: 10
return 0;
}

unique_ptr自定义删除器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <memory>
#include <iostream>
#include <functional>
using namespace std;

class MyDeletor {
public:
void operator() (int* p) {
cout << "deletor: " << *p << endl;
delete p;
}
};

int main() {
unique_ptr<int, MyDeletor> uptr(new int(5), MyDeletor());
// unique_ptr<int, function<void (int*)>> uptr(new int(5), [](int *p)->void {
// cout << "deletor: " << *p << endl;
// delete p;
// });
cout << "value: " << *uptr << endl;
return 0;
}

unique_ptr 做函数参数

  • unique_ptr不能被复制,只可以move,即当要让unique_ptr 的变量做函数参数,直接将变量放在括号里是不可以的,可以用move函数,如果值是引用的话,可以不用move
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <memory>
#include <iostream>
#include <string>
using namespace std;

class Cat {
private:
string name_ = "mimi";

public:
Cat(string name) : name_(name) {}

void CatInfo()
{
cout << "name: " << name_ << endl;
}
};

void OutputCatName(unique_ptr<Cat>& cat)
{
cat->CatInfo();
}

void OutputCatName2(unique_ptr<Cat> cat)
{
cat->CatInfo();
}

int main() {
unique_ptr<Cat> uCat(new Cat("mimimi"));
OutputCatName(uCat); // name: mimimi
// OutputCatName2(uCat);
/*unicall.cpp: In function ‘int main()’:
unicall.cpp:32:24: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Cat; _Dp = std::default_delete<Cat>]’
32 | OutputCatName2(uCat);
| ^
In file included from /usr/include/c++/9/memory:80,
from unicall.cpp:1:
/usr/include/c++/9/bits/unique_ptr.h:414:7: note: declared here
414 | unique_ptr(const unique_ptr&) = delete;
| ^~~~~~~~~~
unicall.cpp:24:37: note: initializing argument 1 of ‘void OutputCatName2(std::unique_ptr<Cat>)’
24 | void OutputCatName2(unique_ptr<Cat> cat)
| ~~~~~~~~~~~~~~~~^~~
*/
OutputCatName2(std::move(uCat)); // name: mimimi
uCat->CatInfo(); // Segmentation fault (core dumped)
return 0;
}

【总结】

1.从上面源码中能明显看到,unique_ptr delete了拷贝构造函数和operator=赋值重载函数,禁止显示的拷贝和赋值,防止了浅拷贝问题的发生。

2.unique_ptr提供了带右值引用参数的拷贝构造和赋值重载函数

3.unique_ptr提供了get()、release()、reset()、swap()等函数,重载了->、*、bool 等运算符

auto_ptr

auto_ptr是C++98中的一种智能指针,用于提供一种自动内存管理的机制。然而在C++11及以后的版本中,auto_ptr已经被弃用,并且在C++17中被完全移除了,推荐使用unique_ptrshared_ptrweak_ptr等更加安全和高效的智能指针。

auto_ptr处理浅拷贝的问题,是直接把前面的auto_ptr都置为nullptr,只让最后一个auto_ptr持有资源

【示例1】auto_ptr在拷贝构造和赋值重载函数中进行隐式所有权转移

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
auto_ptr<int> p1(new int(5));
/*
经过拷贝构造,p2指向了new int资源,
p1现在为nullptr了,如果使用p1,相当于
访问空指针
*/
auto_ptr<int> p2 = p1;
*p1 = 10; // 访问空指针,程序奔溃
return 0;
}

【示例2】auto_ptr在容器中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
vector<auto_ptr<int>> vec;
vec.push_back(auto_ptr<int>(new int(1)));
vec.push_back(auto_ptr<int>(new int(2)));
vec.push_back(auto_ptr<int>(new int(3)));
cout << *vec[0] << endl; //输出1
vector<auto_ptr<int>> vec2 = vec;
/* 这里由于上面做了vector容器的拷贝,相当于容器中
的每一个元素都进行了拷贝构造,原来vec中的auto_ptr
全部为nullptr了,再次访问就成访问空指针了,程序崩溃
*/
cout << *vec[0] << endl; // 访问空指针,程序崩溃
return 0;
}

scoped_ptr

scoped_ptr私有化了拷贝构造函数和operator=赋值函数,从而杜绝了浅拷贝问题的发生,由于不能拷贝和赋值,所有不能应用在容器中。

作者:张泽中

【参考】std::unique_ptr - cppreference.com

[https://zhuanlan.zhihu.com/p/355238160

https://blog.csdn.net/mankeywang/article/details/130287014