手动实现 shared_ptr
面试写了一个基础的 scoped_ptr,被面试官要求写 shared_ptr,一时语塞。面试官不断提示我说在现有的基础上实现 shared_ptr 很简单,真的很简单,宛如在不断暗示我 1+1 就是把两个数加在一起。我知道简单,也知道引用计数原理,但没有写过代码啊,不知道具体是怎么实现引用计数的,当时只能放弃了。
今天研究了一下 shared_ptr,手写了简单实现,现在记录一下实现的重点。
引用计数
面试时,我想是不是用 static 实现的引用计数?其实不能这样做,静态变量是同属一个类所有对象,这样的话这个类的所有对象,不管指向的是不是相同的内存,它们共享的都是同一个计数器。
引用计数,就是在内存中开辟一块内存专门用于存储资源被引用的次数,自己保留一个指向计数器的指针,计数器初始化为 1。每次用已有的 shared_ptr 构造新的 shared_ptr 或者给已有的 shared_ptr 赋值时,通过指针将计数器加 1,所有指向同一个资源的 shared_ptr 也指向同一个计数器。
释放所有权
当 shared_ptr 析构或主动释放所有权时,应将引用计数减 1,然后判断引用计数是否归零,如果归零,那么就释放指针指向的内存,此后将计数器指针置为空。需要注意的是,如果将计数器指针置为空,为了防止指针主动释放所有权之后计数器指针为空,导致判断计数是否归零,需要在判断计数归零前先判断计数指针是否为空。
参考代码
#include <iostream>
#include <string>
template <typename T>
class SharedPtr
{
private:
// 裸指针.
T *_ptr;
// 指向引用计数器的指针.
int *_pcount;
public:
SharedPtr(T *ptr) : _ptr(ptr), _pcount(new int(1))
{
std::cout << "SharedPtr " << this << " constructed!\n\n";
}
// 复制构造函数.
SharedPtr(const SharedPtr<T> &s) : _ptr(s._ptr), _pcount(s._pcount)
{
std::cout << "Entering copy constructor\n";
std::cout << "\tSharedPtr " << this
<< " copied from SharePtr " << &s << std::endl;
// 引用计数加一.
std::cout << "\tReference count increased by one\n";
++(*(_pcount));
std::cout << "Leaving copy constructor\n\n";
}
~SharedPtr()
{
std::cout << "Entering destructor\n";
// 重置指针为空.
this->_ptr = nullptr;
std::cout << "\tReference count decreased by one\n";
// 引用计数减一.
if (_pcount != nullptr && --(*(this->_pcount)) == 0)
{
delete this->_ptr;
_ptr = nullptr;
delete _pcount;
_pcount = nullptr;
std::cout << "\tCount zero, memory released\n";
}
// 将引用计数指针置为空.
this->_pcount = nullptr;
std::cout << "SharedPtr " << this << " destructed\n";
}
public:
// 返回引用计数的个数.
int use_count() const
{
return *(this->_pcount);
}
int use_count()
{
return *(this->_pcount);
}
// 返回指针是否具有独占权, 即引用计数是否为 1.
bool unique() const
{
return *(this->_pcount) == 1 ? true : false;
}
bool unique()
{
return *(this->_pcount) == 1 ? true : false;
}
// 返回裸指针.
T *get() const
{
return this->_ptr;
}
// 释放当前指针的资源所有权, 计数减一.
void release()
{
std::cout << "\tRelease resource ownership\n";
// 重置指针为空.
this->_ptr = nullptr;
std::cout << "\tReference count decreased by one\n";
// 将当前资源引用计数减一.
if (_pcount != nullptr && --(*(this->_pcount)) == 0)
{
delete this->_ptr;
delete this->_pcount;
this->_pcount = nullptr;
std::cout << "\tCount zero, memory released\n";
}
// 将引用计数指针置为空.
this->_pcount = nullptr;
}
public:
SharedPtr<T> &operator=(const SharedPtr<T> &s)
{
std::cout << "Entering assignment operator\n";
if (this == &s)
{
return *this;
}
// 释放原来的旧资源.
release();
// 新资源计数加一.
std::cout << "\tReference count increased by one\n";
this->_ptr = s._ptr;
this->_pcount = s._pcount;
++(*(this->_pcount));
std::cout << "Leaving assignment operator\n\n";
return *this;
}
T &operator*()
{
return *(this->_ptr);
}
T *operator->()
{
return this->_ptr;
}
};
int main()
{
SharedPtr<std::string> p1(new std::string("Hello world!"));
SharedPtr<std::string> p2(p1);
SharedPtr<std::string> p3 = p2;
p3 = p2;
std::cout << p2.use_count() << std::endl;
p1.release();
std::cout << p2.use_count() << std::endl;
std::cout << *(p2.get()) << std::endl;
return 0;
}
输出如下
SharedPtr 0x7ffffffedba0 constructed!
Entering copy constructor
SharedPtr 0x7ffffffedbb0 copied from SharePtr 0x7ffffffedba0
Reference count increased by one
Leaving copy constructor
Entering copy constructor
SharedPtr 0x7ffffffedbc0 copied from SharePtr 0x7ffffffedbb0
Reference count increased by one
Leaving copy constructor
Entering assignment operator
Release resource ownership
Reference count decreased by one
Reference count increased by one
Leaving assignment operator
3
Release resource ownership
Reference count decreased by one
2
Hello world!
Entering destructor
Reference count decreased by one
SharedPtr 0x7ffffffedbc0 destructed
Entering destructor
Reference count decreased by one
Count zero, memory released
SharedPtr 0x7ffffffedbb0 destructed
Entering destructor
Reference count decreased by one
SharedPtr 0x7ffffffedba0 destructed
手动实现 shared_ptr的更多相关文章
- C++11 shared_ptr 智能指针 的使用,避免内存泄露
多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...
- 关于智能指针boost::shared_ptr
boost库中的智能指针shared_ptr, 功能强大, 且开销小,故受到广大coder的欢迎. 但在实际的使用过程中,笔者也发现了一些不足. 1.定制的删除器 shared_ptr除了可以使用默认 ...
- CentOS6.5升级手动安装gcc4.8.2
一.简易安装 操作环境 CentOS6.5 64bit,原版本4.4.7,不能支持C++11的特性~,希望升级到4.8.2 不能通过yum的方法升级,需要自己手动下载安装包并编译 1.1 获取安装包并 ...
- 【STL学习】智能指针之shared_ptr
前面已经学习过auto_ptr,这里补充另外一种智能指针,比auto_ptr要更强力更通用的shared_ptr. shared_ptr 简介及使用选择 几乎所有的程序都需要某种形式的引用计数智能指 ...
- [转] weak_ptr解决shared_ptr环状引用所引起的内存泄漏
http://blog.csdn.net/liuzhi1218/article/details/6993135 循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引 ...
- 解决eclipse无法解析shared_ptr
今天心血来潮更新了一下机器上的ubuntu,装了14.04版本,原来是32位的,换成64的之后感觉是快了不少(加了内存).因为不少软件没做备份,包括eclipse,所以只得重装,重装之后的麻烦事儿就是 ...
- c++继承构造子类调用父类构造函数的问题及关于容器指针的问题及当容器里储存指针时,记得要手动释放
看下面的一个问题: class Person { private: string name; public: Person(const string& s=""){ nam ...
- stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结
stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结 1. auto_ptrauto_ptr主要是用来解决资源自动释放的问题,比如如下代码:voi ...
- weak_ptr解决shared_ptr环状引用所引起的内存泄漏[转]
转载:http://blog.csdn.net/liuzhi1218/article/details/6993135 循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理 ...
随机推荐
- C#与.NET、CLR、CLI是什么关系?什么是.NET框架
1.C#与.NET.CLR.CLI是什么关系?什么是.NET框架? 这个问题好专业啊!一句话两句话还真不好说清.您听说过C++中有个COM的概念吧?您听说过JAVA里的虚拟机吧?CLR(公共 ...
- ajax传字符串时出现乱码问题的解决
字符乱码的解决: 第一种在@RequestMapping中添加 @RequestMapping(value="queryAllToTree",method=RequestMetho ...
- C#异步编程 Task await的理解
async/await是C#5.0中推出的,先上用法: static void Main(string[] args) { Console.WriteLine("-------主线程启动-- ...
- Hibernate脑图
- 备忘:Linux内核编程的几个注意事项
虚拟地址转物理地址要用__pa 内核程序创建的一段地址连续的共享内存,通过内存映射可以让用户态进程存取.之前在RHEL/CentOS的x86_64架构上工作正常.后来在aarch64架构的银河麒麟(L ...
- kubebuilder实战之八:知识点小记
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- MySQL之连接查询和子查询
多表连接的基本语法 多表连接,就是将几张表拼接为一张表,然后进行查询 select 字段1, 字段2, ... from 表1 {inner|lift|right} join 表2 on 连接条件; ...
- Git工具的使用教程二
1.3时光穿梭机--版本回退 版本回退分为两步骤进行操作: 步骤: 1.查看版本,确定需要回到的时候点 指令: git log git log ...
- 安装配置Linux Squid代理服务器
1.代理服务器的工作机制 代理服务器的工作机制像生活中的代理商,假设自己的机器为A,想获得的数据由服务器B提供,代理服务器为C,那么连接过程是,A需要B的数据,并直接和C连接:C接受到A的数据请求之后 ...
- js基本数据类型之间的转换
常见五大基本数据类型 1.number 2.string 3.boolean 4.undefined 5.null 一.转换为string ①调用toString() 方法 因为null和undefi ...