面试写了一个基础的 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的更多相关文章

  1. C++11 shared_ptr 智能指针 的使用,避免内存泄露

    多线程程序经常会遇到在某个线程A创建了一个对象,这个对象需要在线程B使用, 在没有shared_ptr时,因为线程A,B结束时间不确定,即在A或B线程先释放这个对象都有可能造成另一个线程崩溃, 所以为 ...

  2. 关于智能指针boost::shared_ptr

    boost库中的智能指针shared_ptr, 功能强大, 且开销小,故受到广大coder的欢迎. 但在实际的使用过程中,笔者也发现了一些不足. 1.定制的删除器 shared_ptr除了可以使用默认 ...

  3. CentOS6.5升级手动安装gcc4.8.2

    一.简易安装 操作环境 CentOS6.5 64bit,原版本4.4.7,不能支持C++11的特性~,希望升级到4.8.2 不能通过yum的方法升级,需要自己手动下载安装包并编译 1.1 获取安装包并 ...

  4. 【STL学习】智能指针之shared_ptr

    前面已经学习过auto_ptr,这里补充另外一种智能指针,比auto_ptr要更强力更通用的shared_ptr. shared_ptr 简介及使用选择  几乎所有的程序都需要某种形式的引用计数智能指 ...

  5. [转] weak_ptr解决shared_ptr环状引用所引起的内存泄漏

    http://blog.csdn.net/liuzhi1218/article/details/6993135 循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引 ...

  6. 解决eclipse无法解析shared_ptr

    今天心血来潮更新了一下机器上的ubuntu,装了14.04版本,原来是32位的,换成64的之后感觉是快了不少(加了内存).因为不少软件没做备份,包括eclipse,所以只得重装,重装之后的麻烦事儿就是 ...

  7. c++继承构造子类调用父类构造函数的问题及关于容器指针的问题及当容器里储存指针时,记得要手动释放

    看下面的一个问题: class Person { private: string name; public: Person(const string& s=""){ nam ...

  8. stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结

    stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结 1. auto_ptrauto_ptr主要是用来解决资源自动释放的问题,比如如下代码:voi ...

  9. weak_ptr解决shared_ptr环状引用所引起的内存泄漏[转]

    转载:http://blog.csdn.net/liuzhi1218/article/details/6993135 循环引用: 引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理 ...

随机推荐

  1. C#中使用WavHelper保存录音数据为wav文件

    C#将录音数据文件保存为wav格式文件,这里使用到的是WavHelper工具类. WavHelper工具类: using System; using System.Collections.Generi ...

  2. 04.SpringMVC之用

    分析 Spring MVC 是怎么处理请求的.首先分析 HttpServletBean.FrameworkServlet 和 DispatcherServlet 这三个 Servlet 的处理过程,最 ...

  3. 在localStorage中存储对象数组并读取

    频繁ajax请求导致页面响应变慢. 于是考虑将数据存储在window.storage中,这样只请求一次ajax,而不需要频繁请求. 鉴于localstorage中只能存储字符串,所以我们要借助于JSO ...

  4. hdu1002 大数相加问题

    这个题对于 几个月前的我简直是噩梦  好在磕磕绊绊终于写出来了 由于自己的问题  还被巨巨嘲讽了 1 1.#include<stdio.h> 2 2.#include<string. ...

  5. vmware 配置不同网段双网卡。

    一.前言 需求:由于LVS演练需要,需要配置两张linux OS网卡,而且是不同网段. 准备: 物理机:单网卡 VMware:centos 6.8 二.配置 第一步:新建虚拟机VMware,cento ...

  6. centos7 netstat

    netstat 是控制台命令,它可以显示路由表.实际的网络连接以及每一个网络接口设备的状态信息.Netstat 用于显示与 IP . TCP . UDP 和 ICMP 协议相关的统计数据,一般用于检验 ...

  7. QT学习日记篇-03-仿写一个智能家居界面

    课程大纲: <1>让界面漂亮起来,仿写一个智能家居界面 ->第一:给QT工程添加图片 进入下一步: <注意路径和名称一定不能有中文>                   ...

  8. K8S命令行工具——kubectl

    1.kubectl概述 2.kubectl命令的语法 例子: 3.kubectl子命令使用分类 (1)基础命令 (2)部署和集群管理命令 (3)故障和调试命令 (4)其他命令 4.kubectl命令例 ...

  9. MySQL——InnoDB事务

    事务:全部成功 或 全部失败! ------------------------------------------------------------------------------------ ...

  10. Java中使用DOM4J来生成xml文件和解析xml文件

    一.前言 现在有不少需求,是需要我们解析xml文件中的数据,然后导入到数据库中,当然解析xml文件也有好多种方法,小编觉得还是DOM4J用的最多最广泛也最好理解的吧.小编也是最近需求里遇到了,就来整理 ...