0%

C++:智能指针实现

智能指针

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
template <typename T>
class smart_ptr
{
public:
explicit smart_ptr(T *ptr = nullptr) : ptr_(ptr) {}
~smart_ptr() { delete ptr_; }
T *get() const { return ptr_; }

// 重载运算符,让行为更像指针
T &operator*() const { return *ptr_; }
T *operator&() const { return ptr_; }
operator bool() const { return ptr_; }

// // 禁止拷贝,至少可以防止double delete的行为
// smart_ptr(const smart_ptr &) = delete;
// smart_ptr &operator=(const smart_ptr &) = delete;
// // 但禁止拷贝不太方便

// 希望在拷贝时自动移动资源
smart_ptr(smart_ptr &other)
{
ptr_ = other.release();
}
smart_ptr &operator=(smart_ptr &rhs)
{
// 拷贝+交换的惯用法,实际上是移动+交换
// 相当于先将rhs的资源转移给这个临时对象,再把这个临时对象的资源给this
smart_ptr(rhs).swap(*this);
return *this;
}
T *release()
{
T *ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
void swap(smart_ptr &rhs)
{
swap(ptr_, rhs.ptr_);
}

private:
T *ptr_;
};

存在危险行为

1
2
3
4
5
6
7
8
9
template <typename T>
void print_addr(smart_ptr<T> ptr)
{
cout << "Real address of shape is " << static_cast<void *>(&*ptr) << endl;
}
...
smart_ptr<int> ptr(new int(3));
print_addr(ptr);
cout << "Now address of shape is "<< static_cast<void *>(&*ptr) << endl;
1
2
Real address of shape is 0xe22130
Now address of shape is 0

问题: ptr 变成了空指针,原因: print_addr 为值传递,调用函数 smart_ptr(smart_ptr &other) ,将 ptr 传进函数内部对象并置空,离开作用域内部对象被销毁。

危险行为: auto_ptr

右值引用改进——> unique_ptr

1
2
3
4
5
6
7
8
9
10
11
 // 移动构造函数
smart_ptr(smart_ptr &&other)
{
ptr_ = other.release();
}
// 在进入赋值函数时,先构造一个新的rhs(根据构造函数功能进行构造,拷贝/移动)
smart_ptr &operator=(smart_ptr rhs)
{
rhs.swap(*this);
return *this;
}
1
2
3
4
5
6
7
smart_ptr<int> ptr1{new int(2)};
smart_ptr<int> ptr2{ptr1}; // 编译错误
smart_ptr<int> ptr3;
ptr3=ptr1; //编译错误
ptr3=move(ptr1);
smart_ptr<int> ptr4{move(ptr3)};
// 无法引用 函数 "smart_ptr<T>::smart_ptr(const smart_ptr<int> &) [其中 T=int]" (已隐式声明) -- 它是已删除的函数

完善行为:子类指针向基类转换

1
2
3
4
5
6
7
8
9
10
11
12
// 子类指针向基类转换
template <typename U>
smart_ptr(smart_ptr<U> &&other)
{
ptr_ = other.release();
}
......// 编译错误:invalid conversion from 'A*' to 'B*'
smart_ptr<A> ptr1(new A());
smart_ptr<B> ptr2=move(ptr1);
......// 编译成功:子类指针可以转换为基类指针
smart_ptr<B> ptr1(new B());
smart_ptr<A> ptr2=move(ptr1);

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
#include <iostream>
using namespace std;

template <typename T>
class smart_ptr
{
public:
explicit smart_ptr(T *ptr = nullptr) : ptr_(ptr) {}
~smart_ptr()
{
delete ptr_;
// cout << "析构函数" << endl;
}
T *get() const { return ptr_; }

// 重载运算符,让行为更像指针
T &operator*() const { return *ptr_; }
T *operator&() const { return ptr_; }
operator bool() const { return ptr_; }

// 禁止拷贝,至少可以防止double delete的行为
smart_ptr(const smart_ptr &) = delete;
smart_ptr &operator=(const smart_ptr &) = delete;

// 移动构造函数
smart_ptr(smart_ptr &&other)
{
ptr_ = other.release();
}
// 在进入赋值函数时,先构造一个新的rhs(根据构造函数功能进行构造,拷贝/移动)
smart_ptr &operator=(smart_ptr rhs)
{
rhs.swap(*this);
return *this;
}
// 子类指针向基类转换
template <typename U>
smart_ptr(smart_ptr<U> &&other)
{
ptr_ = other.release();
}

T *release()
{
T *ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
void swap(smart_ptr &rhs)
{
swap(ptr_, rhs.ptr_);
}

private:
T *ptr_;
};

shared_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
template <typename T>
class smart_ptr
{
public:
// 构造函数
explicit smart_ptr(T *ptr = nullptr) : ptr_(ptr)
{
if (ptr)
shared_count_ = new shared_count();
}
// 析构函数
~smart_ptr()
{
// 如果ptr不为空并且引用计数减1之后为空释放资源
if (ptr_ && !shared_count_->reduce_count())
{
delete ptr_;
delete shared_count_;
}
}
// 特别:除了提供模板化的拷贝构造以外还要提供非模板的拷贝构造
// 原因:C++不认为下面这种形式的拷贝构造为拷贝构造,所以会生成默认的拷贝构造函数(这种实现实际是错误的)
// 所以:需要实现非模板的拷贝构造
smart_ptr(const smart_ptr &other)
{
ptr_ = other.ptr_;
if (ptr_)
{
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}
// 但不需要提供移动构造函数,因为C++发现提供了拷贝构造函数,也不会默认生成移动构造函数

// 带模板的拷贝构造:实现子类向基类的转换
template <typename U>
smart_ptr(const smart_ptr<U> &other)
{
ptr_ = other.ptr_;
if (ptr_)
{
other.shared_count_->add_count();
shared_count_ = other.shared_count_;
}
}
// 带模板的移动构造
template <typename U>
smart_ptr(smart_ptr<U> &&other)
{
ptr_ = other.ptr_;
if (ptr_)
{
shared_count_ = other.shared_count_;
other.ptr_ = nullptr;
}
}

private:
T *ptr_;
shared_count *shared_count_;
};

方便创建智能指针的工具函数

  • make_unique

    • 语义明确,减少重复
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    auto ptr_int = make_unique<int>(42);

    // 如果对象是结构体
    struct Point{
    int x;
    int y;
    };
    auto ptr_point = make_unique<Point>(new Point{1,2}); // 有重复
    // C++20
    auto ptr_point = make_unique<Point>(1,2);
  • make_shared

    • 对象和引用计数一起只进行一次内存分配,提高性能

其他智能指针特性

  • 数组支持
    • unique_ptr<T[]>(C++11)
    • shared_ptr<T[]>(C++17) // C++17之前管理数组需要自定义删除器来释放内存,因为默认使用 delete 不能正确释放分配的数组,需要在自定义删除器中使用 delete[] 释放数组,C++17以后 shared_ptr<T[]> 默认使用 delete[] 来释放管理的对象
  • 自定义删除器
    • 定制智能指针离开作用域之后如何删除管理的对象
    • 作用:在智能指针释放对象时进行一些特殊操作,比如打印日志、管理内存以外的其他资源比如说:文件句柄、数据库连接
    • 使用:可以是函数/类的对象/ lambda表达式
  • weak_ptr
    • 循环引用问题:如果父节点管理子节点,使用 shared_ptr 指向子节点,此时子节点不可以用 shared_ptr 指向父节点,会形成环状依赖,引用计数不会降到0 。此时子节点可以使用 weak_ptr 指向父节点,这样可以保证引用计数最后会降为0,正常析构。
    • 如果可以确定,子节点存在,父节点一定存在的话(不会出现指针悬空),可以考虑使用裸指针,性能更高,使用 weak_ptr 还是有引用计数增减的问题,性能还是有一点影响

智能指针的使用建议

  1. 在指针具有拥有权的时候应当使用 unique_ptrshare_ptr
  2. 尽量使用 unique_ptr,一定需要引用计数才用 share_ptr
  3. 注意对智能指针进行拷贝构造或者赋值会影响对象的生命周期(因为这些操作会改变与智能指针相关的引用计数),进而影响所管理对象的生命周期
  4. 传参一般仍使用普通指针或引用,除非要传递所有权