类-share_ptr实现原理

本文探讨了C++中`shared_ptr`的实现原理,重点在于引用计数如何管理内存。通过拷贝构造和拷贝赋值操作,`shared_ptr`能够智能地增加或减少引用计数,从而决定何时释放内存。同时,文章提到了`HasPtr`类作为示例,用于说明引用计数的具体应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.引用计数管理内存的原理
C++新标准库中采两个智能指针类型来管理动态对象,share_ptr允许多个指针指向同一个对象;
unique_ptr则“独占”所指对象。
对于share_ptr<T>我们可以按如下方式使用去管理堆内存,相比于new,省去使用delete去释放内存的麻烦。
void use_factory(T arg)
{
	share_ptr<Foo> p = factory(arg);
	Foo *ptr = new Foo(arg);
	
	//使用P
}//p离开了作用域,但它指向的内存会被释放掉
 //ptr申请的内存没有被释放,造成了内存泄漏

我们知道指针或引用在离开作用域时是不会进行析构的,但类在离开作用域时会自动执行析构函数(从这也可看出share_ptr本质是类,只是模仿出指针使用的效果而已),因此,
我们可以通过析构函数调用delete去销毁资源。
那share_ptr如何实现多个类同时指向同一个对象,这就产生了引用计数,它实际是一个整形指针,让多个
对象共享其指向的对象,该对象记录着当前有多少个对象在共享同一变量
share_ptr<Foo> get_factory(T arg)
 {
	share_ptr<Foo> p = factory(arg);  //引用计数值为1,同时new一个Foo对象
	
	share_ptr<Foo> tmp = factory(arg);//创建一个临时的tmp, 
	tmp = p; //进行赋值操作时,右侧P递增引入计数值为2,左侧tmp递减引入计数值为0,
			 //销毁上句创建的资源后再将指针指向与p同样的资源
	
	return p; //当返回p时,引用计数进行了递增操作值为3
	
 }//tmp离开作用域时,调用析构函数引用计数递减到2
  //p离开作用域时,调用析构函数引用计数递减到1,表示还有一个对象享有Foo对象

根据上述例子我们知道shared_ptr通过拷贝构造和拷贝赋值函数递增或递减引用计数。

2.引用计数的实现(可参考c++ primer 13.2.2章节)

示例中定义了一个HasPtr类去管理一个string对象,其类的定义如下,为了便宜调试,每个类

均有打印信息。

①类定义

#include <string>
#include <iostream>
using std::string;
using std::cout;
using std::endl;
using std::ostream;
 
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
class HasPtr{
	friend ostream& operator<<(ostream&, const HasPtr&);
	
	friend void swap(HasPtr& lhs, HasPtr& rhs); 
		 
public:
	//构造函数 
	HasPtr(const string &s = string()):ps(new string(s)),use(new std::size_t(1)) 
		{cout<< "call: HasPtr()"<<endl;}		
	HasPtr(const char *c):HasPtr(string(c)){}
	
	//构造函数 
	HasPtr(const HasPtr& p):ps(p.ps), use(p.use)
		{++*use; cout<< "call: HasPtr(const HasPtr& p)"<<endl;}  
		
	//拷贝构造函数
	HasPtr& operator=(HasPtr p);
	
	//虚构函数 
	~HasPtr();
	
private:
	string      *ps;
	std::size_t *use;
};
②.拷贝赋值的实现
拷贝赋值时为了避免自赋值的情况,本例采用swap的方式实现,基本过程是左侧对象执行一次析构后执行拷贝构造操作。
inline
void swap(HasPtr& lhs, HasPtr& rhs){
	using std::swap;
	swap(lhs.ps, rhs.ps);
	swap(lhs.use, rhs.use); 	
} 

inline 
HasPtr& HasPtr::operator=(HasPtr p){
	cout<< "void swap(HasPtr& lhs, HasPtr& rhs)"<<endl;	
	using std::swap;
	swap(*this, p);	
	return *this;
} 
③.析构的实现
HasPtr::~HasPtr()
{
	cout<< "HasPtr::~HasPtr():"<< *this <<endl;	
	if(--*use == 0)
	{
		cout<< "delete ps; delete use;"<<endl;	
		delete ps;
		delete use;
	}		
}
④.打印函数
inline
ostream& operator<<(ostream& os, const HasPtr& ptr)
{
	os << *ptr.ps << " " << *ptr.use; 
	return os; 
}
⑤.调试程序
int main(int argc, char** argv) 
{
	cout << "1.构造函数" << endl;
	HasPtr ptr1("first"); 				//打印:call: HasPtr()
	HasPtr ptr2("sencond");				//打印:call: HasPtr()
	cout << "" << endl; 

	cout << "2.拷贝构造" << endl;
	HasPtr ptr3(ptr2);			//打印:call: HasPtr(const HasPtr& p)
	cout << "ptr1: " << ptr1 << endl; //打印:ptr1: first 1
	cout << "ptr2: " << ptr2 << endl; //打印:ptr2: sencond 2
	cout << "ptr3: " << ptr3 << endl; //打印:ptr3: sencond 2
	cout << "" << endl; 

	cout << "3.不同对象之间的赋值" << endl; 
	ptr1 = ptr3;	//打印:call: HasPtr(const HasPtr& p),
					//HasPtr& HasPtr::operator=(HasPtr p)
					//HasPtr::~HasPtr():first 1
					//delete ps; delete use;
	cout << "ptr1: " << ptr1 << endl; //打印:ptr1: sencond 3
	cout << "ptr2: " << ptr2 << endl; //打印:ptr2: sencond 3
	cout << "ptr3: " << ptr3 << endl; //打印:ptr3: sencond 3
	cout << "" << endl; 

	cout << "4.指向类型相同赋值" << endl; 
	ptr1 = ptr3;	//打印:call: HasPtr(const HasPtr& p)
					//HasPtr& HasPtr::operator=(HasPtr p)
					//HasPtr::~HasPtr():first 1
					//delete ps; delete use;
	cout << "ptr1: " << ptr1 << endl; //打印:ptr1: sencond 3
	cout << "ptr2: " << ptr2 << endl; //打印:ptr2: sencond 3
	cout << "ptr3: " << ptr3 << endl; //打印:ptr3: sencond 3
	cout << "" << endl; 

	cout << "5.自符值" << endl;
	ptr1 = ptr1;   //打印:call: HasPtr(const HasPtr& p)
					//HasPtr& HasPtr::operator=(HasPtr p)
					//HasPtr::~HasPtr():sencond 4
	cout << "ptr1: " << ptr1 << endl; //打印:ptr1: sencond 3
	cout << "ptr2: " << ptr2 << endl; //打印:ptr2: sencond 3
	cout << "ptr3: " << ptr3 << endl; //打印:ptr3: sencond 3
	cout << "" << endl; 

	cout << "程序结束" << endl;
	//打印:
	//HasPtr::~HasPtr():sencond 3
	//HasPtr::~HasPtr():sencond 2
	//HasPtr::~HasPtr():sencond 1
	//delete ps; delete use;
	return 0;
}






<think>嗯,用户让我介绍一下shared_ptr,我需要用中文详细解释清楚。首先,我应该从基础概念入手,解释shared_ptr是什么,属于智能指针的一种,然后说明它的作用和管理内存的方式。可能需要对比其他型的智能指针,比如unique_ptr和weak_ptr,来突出shared_ptr的特点。 接下来要讲清楚shared_ptr的核心机制,也就是引用计数。这里要说明引用计数是如何工作的,当有新的shared_ptr指向同一个对象时,计数增加,反之则减少,当计数归零时自动释放内存。这部分可能需要举例说明,比如创建多个shared_ptr实例时的情况。 然后,用户可能需要了解如何正确使用shared_ptr。这时候应该提到初始化方法,比如使用make_shared而不是直接new,这样可以提高效率和安全性。还要强调循环引用的问题,以及如何用weak_ptr来避免,这是常见的问题点,必须详细说明。 另外,使用场景也是重点。需要说明在哪些情况下适合使用shared_ptr,比如多个对象共享同一资源时。同时,也要提到性能考虑,因为引用计数会有一定的开销,不适合在性能敏感的场景滥用。 可能用户对线程安全也有疑问,这里需要解释shared_ptr本身的线程安全性,指出引用计数的原子操作是线程安全的,但对象访问需要额外同步。这部分需要准确,避免误导。 最后,常见错误和注意事项也需要涵盖,比如不要混合使用原始指针和shared_ptr,避免内存泄漏或重复释放。还可以举一个简单的代码例子,展示基本用法,并指出循环引用的例子,以及如何用weak_ptr解决。 需要确保整个回答结构清晰,分点说明,语言通俗易懂,避免过于技术化的术语,同时覆盖用户可能关心的各个方面。检查是否有遗漏的部分,比如构造函数、自定义删除器的用法,是否需要简要提及?可能对于初级用户来说,先掌握基本用法更重要,高级用法可以稍后学习,所以暂时不深入。 还要注意用户可能对引用计数实现细节感兴趣,但可能不需要太底层的解释,所以保持在高层次的概念层面即可。确保每个部分之间有逻辑衔接,让读者能够逐步理解shared_ptr的工作原理和使用方法。 总结下来,回答的结构应该是:定义和作用、核心机制(引用计数)、基本用法、循环引用问题、使用场景、线程安全、注意事项,最后代码示例。这样分步骤介绍,帮助用户建立全面的理解。</think>以下是对`shared_ptr`的逐步介绍: --- ### 一、定义与作用 `shared_ptr`是C++标准库提供的**智能指针**,用于管理动态分配的内存。它通过**引用计数**机制实现多个指针共享同一对象,确保当最后一个`shared_ptr`离开作用域时,对象被自动释放。 --- ### 二、核心机制:引用计数 1. **引用计数原理** 每个`shared_ptr`内部维护一个计数器: - 当新`shared_ptr`指向同一对象时,计数**+1** - 当`shared_ptr`被销毁(如离开作用域)时,计数**-1** - 当计数归零时,自动调用`delete`释放对象 2. **示例说明** ```cpp std::shared_ptr<int> p1(new int(42)); // 引用计数=1 { std::shared_ptr<int> p2 = p1; // 引用计数=2 } // p2销毁,引用计数=1 ``` `p1`销毁时,引用计数归零,内存自动释放。 --- ### 三、基本用法 1. **初始化方式** - 推荐使用`std::make_shared`(更高效、安全): ```cpp auto p = std::make_shared<int>(42); ``` - 直接初始化(需避免混用原始指针): ```cpp std::shared_ptr<int> p(new int(42)); ``` 2. **访问对象** ```cpp std::cout << *p << std::endl; // 解引用 p->method(); // 访问成员 ``` --- ### 四、循环引用问题 1. **问题描述** 当两个`shared_ptr`互相指向对方时,引用计数无法归零,导致内存泄漏: ```cpp class A { std::shared_ptr<B> b_ptr; }; class B { std::shared_ptr<A> a_ptr; }; auto a = std::make_shared<A>(); auto b = std::make_shared<B>(); a->b_ptr = b; // A引用B,计数=2 b->a_ptr = a; // B引用A,计数=2 ``` 2. **解决方案** 使用`weak_ptr`打破循环: ```cpp class B { std::weak_ptr<A> a_ptr; // 改为weak_ptr }; ``` --- ### 五、使用场景 - 多个对象需要共享同一资源(如缓存、配置文件) - 对象所有权不明确,需动态管理生命周期 --- ### 六、线程安全性 - **引用计数操作是原子的**,线程安全 - **对象访问需额外同步**(如互斥锁) --- ### 七、注意事项 1. 避免混用原始指针与`shared_ptr` ```cpp int* raw_ptr = new int(10); std::shared_ptr<int> p1(raw_ptr); std::shared_ptr<int> p2(raw_ptr); // 错误!重复释放 ``` 2. 不要返回`shared_ptr`管理的原始指针 3. 优先使用`make_shared`而非`new` --- ### 八、代码示例 ```cpp #include <memory> #include <iostream> class Resource { public: Resource() { std::cout << "Resource created\n"; } ~Resource() { std::cout << "Resource destroyed\n"; } }; int main() { auto ptr1 = std::make_shared<Resource>(); // 计数=1 { auto ptr2 = ptr1; // 计数=2 } // 计数=1 return 0; // 计数=0,资源释放 } ``` --- ### 总结 `shared_ptr`通过引用计数简化内存管理,但需注意循环引用和原始指针混用问题。结合`weak_ptr`和`make_shared`可提高代码安全性与效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值