Skip to content

TongVan520/SmartObject

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SmartObject 智能对象库

可以解决的问题:

# 问题
1 上使用线程安全智能指针
2 安全地使用 指向其他智能指针所管理的数据 的指针

智能对象SmartObject

一个SmartPointer类的辅助类。

智能包装器SmartWrapper

可以将满足以下所有条件的类型包装为一个智能对象

  • 该类型可被构造
  • 该类型继承自SmartObject

智能指针SmartPointer

可以指向任何由智能对象类的实例的智能指针。 它不同于标准库中的std::unique_ptrstd::shared_ptr,与std::weak_ptr有些许相似,下面我们来看看它们的区别:

指针 指针类型示例 可指向的数据 是否影响数据生命周期
原始指针 T*
唯一指针 std::unique_ptr<T>
共享指针 std::shared_ptr<T>
弱指针 std::weak_ptr<T>
SmartPointer指针 SmartPointer<T>

std::unique_ptr

只能指向存储在内存上的数据。
std::unique_ptr所指向的数据的生命周期是由指针本身来控制的,当指针被销毁时,数据也会被销毁。 有点像Rust中的所有权规则。

std::shared_ptr

只能指向存储在内存上的数据。
std::shared_ptr所指向的数据的生命周期是由引用计数来控制的,当引用计数为0时,数据才会被销毁。

std::weak_ptr

只能指向存储在内存上的数据。
std::weak_ptr不会影响被指向的数据的生命周期,如同原始指针那般自由。只不过std::weak_ptr会提供expired接口供程序员判断该指针所指向的数据是否已失效(包括野指针)。
这是原始指针做不到的地方,它无法判断自己是否为野指针
其原理是根据引用计数是否为0来判断被指向的数据是否已被释放。因此指向有效数据的std::weak_ptr一定只能通过std::shared_ptr来创建。

SmartPointer

既能指向存储在内存上的数据,也能指向存储在内存上的数据。
SmartPointer不会影响被指向的数据的生命周期,如同原始指针那般自由。只不过SmartPointer会提供isNull接口供程序员判断该指针所指向的数据是否已失效(包括野指针)。
这是与std::weak_ptr的相似之处。
虽然SmartPointer可以指向存储在内存上的数据,但它不会影响被指向的数据的生命周期,因此它不会自动销毁被指向的数据。

线程安全

考虑以下示例代码:

class Student : public SmartObject {
private:
	string nameStr;
	size_t age = 0;

public:
	virtual ~Student() {
		cout << this << "::" << __FUNCTION__ << endl;
	}
	
	const string& getName() const {
		return nameStr;
	}
	
	void setName(const string& nameStr) {
		this->nameStr = nameStr;
	}
	
	size_t getAge() const {
		return age;
	}
	
	void setAge(size_t age) {
		this->age = age;
	}
};

void mainForThread(SmartPointer<Student> p_student) {
	cout << "子线程id:" << this_thread::get_id() << endl;
	
	while (p_student) {
		// ==========================↓在以下区域delete则抛异常↓==========================
		cout << "======子线程======" << endl;
		// 由于Student类线程不安全,所以无法保证其对象操作的原子性
		// cout << "姓名:" << p_student->getName() << endl;
		// cout << "年龄:" << p_student->getAge() << endl;
		
		// 有概率抛异常,情况为:子线程在while判断后,打印student信息前,主线程delete
		try {
			cout << "姓名:" << (*p_student).getName() << endl;
			cout << "年龄:" << (*p_student).getAge() << endl;
			// ======================↑在以上区域delete则抛异常↑==========================
		}
		catch (const exception& excp) {
			cerr << excp.what() << endl;
		}
	}
	
	cout << "内存已释放..." << endl;
}

void main() {
	cout << "多线程测试开始" << endl;
	cout << "主线程id:" << this_thread::get_id() << endl;
	
	Student* p_student = new Student;
	p_student->setName("田所浩二");
	p_student->setAge(24);
	
	thread thrd(mainForThread, SmartPointer<Student>(p_student));
	
	this_thread::sleep_for(chrono::milliseconds(1000));
	
	delete p_student;
	thrd.join();
	cout << "多线程测试结束" << endl;
}

以下是运行两次的输出:

第一次

多线程测试开始
主线程id:1
子线程id:2
======子线程======
姓名田所浩二
年龄24

(此处省略重复输出的内容...)

======子线程======
姓名田所浩二
年龄24
======子线程======
姓名0x1001d00::~Student田所浩二

年龄24
======子线程======
姓名田所浩二
年龄内存已释放...
多线程测试结束
日期 / 时间Oct 23 2024 / 19:00:19
文件D:/Application Projects/CLion/SmartObject/SmartObject/SmartPointer.h	122
函数operator*
错误信息指针无效...

第二次

多线程测试开始
主线程id:1
子线程id:2
======子线程======
姓名田所浩二
年龄24

(此处省略重复输出的内容...)

======子线程======
姓名田所浩二
年龄24
======子线程======
姓名田所浩二
0x10e1d00年龄:::24~Student
======子线程======
姓名田所浩二

年龄24
内存已释放...
多线程测试结束

实现思路

原始指针最大的痛点在于:

原始指针无法判断自己是否为野指针

而造成野指针的原因只有一个:

指针所指向的数据已被释放,但指针本身并不知情。

因此我们可以在数据的析构函数上做手脚,在自己被销毁时通知所有指向它的指针。

熟悉Qt的开发者们就知道这种功能只需要一个信号就可以解决。

性能

要实现保障了功能的最小性能开销,就得手动实现一个简单的信号槽功能,也许都称不上信号槽,只能算一些简单的回调功能。
但为了保证线程安全,还是选择了现成的sigslot信号槽库。

线程安全

由于sigslot提供的功能是线程安全的,因此只需要保证SmartPointer中修改原始指针的代码是线程安全的即可。

只需用一个互斥锁保护原始指针即可。 考虑到普通的互斥锁会在一个线程读取时会导致需要读取它的其他线程发生阻塞,因此可以将互斥锁替换为读写锁

待解决问题

# 问题 描述
1 不可继承的类型 目前为止TypeWrapper不可继承类型的支持不太友好。例如:不支持到原始类型的隐式类型转换;必须显式调用原始类型的方法等

About

基于信号槽实现的中心化智能对象和智能指针

Resources

Stars

Watchers

Forks

Packages

No packages published