c++11智能指针之shared_ptr实现:迭代器,数组操作,操作符重载
背景介绍:c++方向的硕士狗,实验室没什么c++项目,都是自学,在网上看到说智能指针这块挺重要的,自己也看了不少资料,觉得不如自己动手实战,从零到有实现一个shared_ptr。
规划:首先用c++11实现一个基本的智能指针,包括构造析构、拷贝移动等,然后往里面添加功能,如自定义删除器,支持数组管理的接口,以及目前我认为比较重要的多线程安全等等。最后采用c++17和20重构代码,看有没有什么需要改进的。
在代码实现中,我会一块附上如何调用这些接口,方便自己和大家学习
笔者也是刚入门c++,可能会有很多错误,欢迎大家指正,也欢迎大家一起学习,有任何问题可以留言评论
本节内容:本篇将会介绍在系列文章一:基本功能的框架上==扩展支持数组的功能,然后添加迭代器功能,方便用户对数组进行迭代操作。最后添加操作符重载函数,比如智能指针的解引用和比较操作。
文章目录
一、支持数组操作
这个其实就可以写很多接口,比如支持数组的下标索引、动态管理数组大小、数组的排序查找等,然后添加迭代器接口。
原来的基本框架:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
public:
mySharedSp(T* p = NULL); //构造。类内声明,需要指定默认参数,类外实现不需要再次指定
mySharedSp(const mySharedSp<T>& other); //拷贝构造
mySharedSp<T>& operator = (const mySharedSp<T>& other); //拷贝赋值
mySharedSp(mySharedSp<T>&& other); //移动构造函数
mySharedSp<T>& operator = (mySharedSp<T>&& other); //移动赋值,注意不能将参数设置为const
~mySharedSp(); //析构
};
1. 支持数组的下标索引
因为要支持使用new操作符创建数组,然后用new返回的指针构造智能指针对象,由于系统无法从new返回的指针判断数组大小,并且下标索引还得检查是否越界,因此在构造的时候还得显式地传入一个数组大小的私有变量。
当然可以直接用vector创建数组,然后传入vec.data()作为智能指针管理的指针,下标索引用std::vector的成员函数at(index),这个成员函数自带越界检查。
在这里我还是添加了一个数组大小的私有变量
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size; // 数组大小
public:
...
};
size_t 是一个无符号整数类型,用于表示对象的大小和数组下标。它在不同的平台上可能具有不同的实现,但通常与机器字长相同(例如,在 32 位系统上为 32 位,而在 64 位系统上为 64 位)。
需要注意的是:
m_size数组大小,定义为非指针,这里我有想过使用指针类型来定义数组大小,但考虑到还是避免多个智能指针对象指向同一个数组,因此使用非指针,想听听大家怎么考虑的。
在这里我重新修改代码,将数组大小定义为指针类型,因为考虑到不同的智能指针对象要共享数组的信息。
引入了m_size,那这样在系列文章一:基本功能中的基本框架有些地方就需要变,改动如下:
template<typename T>
class mySharedSp {
private:
int* m_count; //指向计数器的指针
T* m_ptr; //多个智能指针对象共享一个引用计数,因此要定义为指针
size_t* m_size; //数组大小
public:
mySharedSp(T* p = NULL, size_t size = 0); //构造。若调用的时候不指定,初始化指针为空,数组大小为0.
... // 其他成员函数不变
};
// 类外定义
//构造
template<typename T>
mySharedSp<T>::mySharedSp(T* p, size_t size) : m_ptr(p), m_count(new int(1)), m_size(new size(size)) {
cout << "调用构造函数" << endl;
}
//拷贝构造,移动构造类似,在此不展开了
template<typename T>
mySharedSp<T>& mySharedSp<T>::operator = (const mySharedSp<T>& other) {
lock_guard<std::mutex> lock(m_mutex);
++(*other.m_count);
--(*m_count);
if (*m_count == 0) {
delete m_ptr;
m_ptr = NULL; //只delete不置空会产生悬空指针
delete m_count;
m_count = NULL;
delete m_size;
m_size = NULL;
}
m_ptr = other.m_ptr;
m_count = other.m_count;
m_size = other.m_size;
cout << "调用拷贝赋值函数" << endl;
return *this;
}
// 析构不变,因为 m_size 会自动销毁
接下来添加支持数组下标索引函数。
在类中声明成员函数:
因为是通过下标索引 [ ] 访问数组元素,因此是操作符重载,返回的是 T 类型的引用。
再声明一个获取数组大小的函数。
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size; // 数组大小
public:
T& operator[](size_t index) const; //通过 ptr[index] 访问数组元素
size_t size() const; //获取数组大小
...
};
类外定义:
这里为了保证安全性,首先判断是不是空指针,然后用 m_size 保证不越界。
//动态数组管理,根据索引下标
template<typename T>
T& mySharedSp<T>::operator[](size_t index) const {
if (m_ptr != NULL) {
if (index < *m_size) {
return m_ptr[index];
}
else {
throw out_of_range("Index out of range");
}
}
else {
throw out_of_range("Null pointer exception");
}
}
//获取数组大小
template<typename T>
size_t mySharedSp<T>::size() const {
return *m_size;
}
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{1,2,3,4,5}, 5);
int a = sp[2]; //a = 3
int b = sp.size(); // b = 5
return 0;
}
2. 动态管理数组大小resize
提供方法来动态调整数组的大小,类似于动态数组的resize操作。
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
void resize(int newSize); //动态调整数组的大小
...
};
这里将传入的 newSize 定义为int,是为了检查是否传入的参数为负数,是负数则抛出异常。
如果传入一个负数作为 size_t 类型的参数,那么这个负数会被隐式转换为一个非常大的正整数。因此用size_t 类型则在函数中检查 newSize < 0,这个条件永远不会成立。
类外定义:
//调整数组大小
//如果是空指针,调用resize则创建新数组
template<typename T>
void mySharedSp<T>::resize(int newSize) {
if (newSize < 0) { //检查传入的是否为负值
throw invalid_argument("Invalid argument");
return;
}
T* newPtr = new T[newSize](); //值初始化,初始化数组元素全为0
if (m_ptr != NULL) {
size_t eleNumsToCopy = min{ newSize, *m_size };
for (int i = 0; i < eleNumsToCopy; ++i) {
newPtr[i] = m_ptr[i];
}
delete[] m_ptr; // 释放原指针
}
//若原本就是是空指针,resize则直接创建一个新的数组
m_ptr = newPtr; // 将新的数组指针和大小同步给成员变量
*m_size = newSize;
}
这里如果是空指针,调用resize相当于创建了一个全零数组。
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{1,2,3,4,5}, 5);
int a = sp.size(); // a = 5
sp.resize(7); // {1,2,3,4,5,0,0}
a = sp.size(); // a = 7
sp.resize(2); // {1,2,3}
a = sp.size(); // a = 3
return 0;
}
3. 预留数组大小reserve
预留reserve和调整resize功能很像,resize是多的元素补零,少的直接砍掉;reserve是只能预留更大的空间,并且不更新智能指针的数组大小。
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t m_size;
public:
void reserve(int newSize); //动态调整数组的大小
...
};
类外定义:
// 预留空间
template<typename T>
void mySharedSp<T>::reserve(int capacity) {
if (capacity < 0) {
throw invalid_argument("Invalid argument");
return;
}
if (capacity > *m_size) {
// 创建新的数组。这里用默认初始化和值初始化都可以,因为函数中没有改变m_size的值,
//所以不管扩容后后面的值是未定义还是0,调用size()返回的都是原数组size
T* newPtr = new T[capacity];
if (m_ptr != NULL) {
for (size_t i = 0; i < *m_size; ++i) {
newPtr[i] = m_ptr[i]; // 复制元素
}
delete[] m_ptr; // 释放原数组内存
}
m_ptr = newPtr; // 更新指针
//不更新m_size
}
}
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{1,2,3,4,5}, 5);
int a = sp.size(); // a = 5
sp.reserve(7);
a = sp.size(); // a = 5
return 0;
}
4. 数组排序
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
// 数组排序函数,默认整个区间排序。使用lambda表达式,默认为升序排列
void sort(iterator begin = m_ptr, iterator end = m_ptr + *m_size, bool (*comp)(const T&, const T&) = [](const T& a, const T& b) { return a < b;});
...
};
m_ptr + *m_size 是一个指向数组末尾(最后一个元素之后的位置)的指针,它可以作为迭代器使用。
第三个参数使用lambda表达式,
[](const T& a, const T& b) { return a < b;}
这三个部分分别是 捕获列表,参数列表,函数体。
类外定义:
//数组排序
//参数:第三个为函数指针,它指向一个接受两个 const T& 类型的参数并返回bool类型的函数。
//在类内声明中使用lambda表达式,默认为升序排列
template<typename T>
void mySharedSp<T>::sort(iterator begin, iterator end, bool (*comp)(const T&, const T&)) {
std::sort(begin, end, comp);
}
这里面用到了迭代器,也可以传递整数,比如:
// 传递整数作为形参
template<typename T>
void mySharedSp<T>::sort(size_t begin, size_t end, bool (*comp)(const T&, const T&)) {
std::sort(m_ptr + begin, m_ptr + end, comp);
}
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{2,1,3,5,4}, 5);
sp.sort(sp.begin(), sp.end()); // 或者sp.sort();
return 0;
}
5. 数组查找
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
T* find(const T& value) const; //查找,传递的是一个常量引用
...
};
类外定义:
// 数组查找
// 返回一个指向该元素的 T类型 指针
template<typename T>
T* mySharedSp<T>::find(const T& value) const {
return std::find(m_ptr, m_ptr + *m_size, value);
}
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{1,2,3,4,5}, 5);
int value = 5;
int* result = sp.find(value);
if (result != sp.end()) {
std::cout << "Found value: " << *result << std::endl;
} else {
std::cout << "Value not found" << std::endl;
}
return 0;
}
二、迭代器
6. 迭代器
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
// using关键字
// iterator 和 const_iterator 是一个类型的别名,因此是成员类型。
using iterator = T*; // 将 iterator 定义为 T* 的别名,比如 T* find() 和 iterator find()是等价的
using const_iterator = const T*; // 常量指针
iterator begin(); // 常量对象调用
iterator end();
const_iterator begin() const; // 非常量对象只能调用这个,函数重载,根据对象的常量性选择不同版本,
const_iterator end() const;
...
};
类外定义:
// 迭代器
template<typename T>
typename mySharedSp<T>::iterator mySharedSp<T>::begin() {
return m_ptr;
}
template<typename T>
typename mySharedSp<T>::iterator mySharedSp<T>::end() {
if (m_ptr != nullptr && m_size != nullptr && *m_size > 0) { // 有效性检查
return m_ptr + *m_size;
}
throw std::runtime_error("Invalid end iterator");
}
template<typename T>
typename mySharedSp<T>::const_iterator mySharedSp<T>::begin() const {
return m_ptr;
}
template<typename T>
typename mySharedSp<T>::const_iterator mySharedSp<T>::end() const {
if (m_ptr != nullptr && m_size != nullptr && *m_size > 0) { // 有效性检查
return m_ptr + *m_size;
}
throw std::runtime_error("Invalid end iterator");
}
由于 iterator 是 mySharedSp 类模板的一个成员类型,因此需要使用 mySharedSp<T>::iterator 来指定返回类型。但是,由于编译器在处理模板代码时会默认 mySharedSp<T>::iterator 为一个 静态数据成员,而不是一个类型,因此需要在前面加上typename关键字来显式地告诉编译器 mySharedSp<T>::iterator 是一个类型。
另外,由于 end() 的返回值根据 m_size 确定,因此在返回之前对 m_size 进行有效性检查,确保不为空且指向的值有效。
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{1,2,3,4,5}, 5);
for (int* it = sp.begin(); it != sp.end(); ++it) {
std::cout << *it << ' ';
}
std::cout << std::endl;
return 0;
}
三、操作符重载和其他接口
7. 解引用
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
T& operator* () const;//解引用,返回指针指向的对象的引用
...
};
函数后面加const,表示该成员函数不会改变成员变量。
类外定义:
//解引用
//创建一个智能指针对象p,*p 就等价于 p.operator*(), 返回的是p管理的指针指向的对象
template<typename T>
T& mySharedSp<T>::operator* () const {
cout << "指针的解引用,得到指针指向的对象" << endl;
return *m_ptr;
}
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{1,2,3,4,5}, 5);
int a = *sp; //根据定义,返回数组第一个元素
return 0;
}
8. 智能指针比较操作
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
bool operator==(const mySharedSp<T>& other) const; //智能指针的比较操作
bool operator!=(const mySharedSp<T>& other) const;
...
};
类外定义:
//智能指针的比较操作
template<typename T>
bool mySharedSp<T>::operator==(const mySharedSp<T>& other) const {
return m_ptr == other.m_ptr;
}
//智能指针的比较操作
template<typename T>
bool mySharedSp<T>::operator!=(const mySharedSp<T>& other) const {
return !(m_ptr == other.m_ptr);
}
智能指针的比较运算符通常只比较它们所指向的对象。
如何调用:
int main(){
mySharedSp<int> sp1(new int[5]{1,2,3,4,5}, 5);
mySharedSp<int> sp2(sp1);
mySharedSp<int> sp3(new int[5]{1,1,1,1,1}, 5);
bool a = sp2 == sp1; // a = true
bool b = sp3 == sp1; // b = false
return 0;
}
9. 成员访问
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
T* operator-> () const;//通过指针访问成员变量,返回的是成员变量,在智能指针类中是一个指针
...
};
类外定义:
//成员访问
template<typename T>
T* mySharedSp<T>::operator-> () const {
cout << "通过指针访问成员变量,得到指针指向的对象" << endl;
return m_ptr;
}
创建一个智能指针对象p,p->xxx 就等价于 p.operator->()->xxx, 返回的是p管理的指针指向的对象的成员变量。
如何调用:
struct Point {
int x;
int y;
};
int main(){
mySharedSp<Point> sp(new Point{1, 2});
int x = sp->x;
int y = sp->y;
std::cout << "Point: (" << x << ", " << y << ")" << std::endl;
return 0;
}
10. 空指针检查
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
bool isNULL() const; //空指针检查
...
};
类外定义:
//空指针检查
template<typename T>
bool mySharedSp<T>::isNULL() const {
return m_ptr == NULL;
}
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{1,2,3,4,5}, 5);
bool a = sp.isNULL(); // a = false
return 0;
}
11. 获取引用数
类内声明:
template<typename T>
class mySharedSp {
private:
int* m_count;
T* m_ptr;
size_t* m_size;
public:
int use_count() const; //获取引用值
...
};
类外定义:
//获取引用数
template<typename T>
int mySharedSp<T>::use_count() const {
return *m_count;
}
如何调用:
int main(){
mySharedSp<int> sp(new int[5]{1,2,3,4,5}, 5);
mySharedSp<int> sp2(sp);
int a = sp2.use_count(); // a = 2
return 0;
}
总结
本节是智能指针支持数组操作,迭代器和操作符重载等接口,下一节会介绍自定义删除器,多线程等。