二、c++11智能指针之shared_ptr代码实现:迭代器,数组操作,操作符重载

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;
}

总结

本节是智能指针支持数组操作,迭代器和操作符重载等接口,下一节会介绍自定义删除器,多线程等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值