背景
智能指针是管理操作内存的重要手段
现在我来实现两个重要的指针,分别是shared_ptr和 unique_ptr, 源代码代码和测试都有,代码中带有注释,因为时间关系,有空再补设计思路和原理吧。
shared_ptr
源代码
//
// Created by zxzx on 2020/10/17.
//
#ifndef ZZZ_SharedPtr_H
#define ZZZ_SharedPtr_H
#include <iostream>
#include <memory>
#include <string>
using namespace std;
template <typename T> //首先,这是一个泛型类
class SharedPtr{
public: // 2个构造函数,一个普通指针的有参,一个拷贝构造函数,重载拷贝运算符,重载->,重载星号运算符。
SharedPtr(T* p=NULL) {
m_refCount = new int(1);// 我们假设这个传入的指针一定是刚刚分配的堆内存的值,如果传入一个已有的指针,那么应该报错,因为可能会重复释放
m_p = p;
};
SharedPtr(const SharedPtr& sp){
m_p = sp.m_p; // 指向同一个内存位置
m_refCount = sp.m_refCount;
++*m_refCount; // 引用数加1,实际指向的值是公用的
}
SharedPtr& operator=(SharedPtr& p){//指向新的其他对象
if(this==&p) return *this; // 注意自己给自己赋值!!!,这里可以优化
decr(); // 释放原来的资源
m_p = p.m_p;
m_refCount = p.m_refCount;
++*p.m_refCount; // p是一个对象,不是一个指针,所以不能写成 p->m_refCount
return *this;
}
T* operator->(){ // 获取指针
if(m_p) return m_p;
throw runtime_error("null pointer1"); // 这里注意异常抛出!!,关于异常,我们还可以继续优化!!
};
T& operator*(){
if(m_p)
return *m_p;
throw runtime_error("null pointer2");
}
~SharedPtr(){
decr();
cout << "deconstructor" << endl;
}
int getRefCount(){ // 这个必须有!
return *m_refCount;
}
private:
T* m_p;
int* m_refCount; // 这个指针
void decr(){
--*m_refCount;
if( *m_refCount == 0){
cout << " free" << endl;
delete m_p;
delete m_refCount;
m_p = NULL;
m_refCount = NULL;
}
}
};
#endif //ZZZ_SharedPtr_H
测试代码
#include <memory>
#include <iostream>
#include "SharedPtr.h"
using namespace std;
class Test{
public:
Test(){name = "";};
Test(char* i) {
name = i;
}
void showName(){
cout << name << endl;
}
string& getName(){
return name;
}
private:
string name; // 不是堆上的对象,析构的时候会自动释放的!!
};
void test_ptr(){
SharedPtr<Test> p1(new Test("abcd"));
p1->showName();
SharedPtr<Test> p2(p1);
*p2 = Test("defg");
p1->showName();
cout <<"@1:" << p1.getRefCount() << endl;
SharedPtr<Test> p3 (new Test("xxx"));
p3 = p2;
cout <<"@2:" << p1.getRefCount() << endl;
}
int main(){
test_ptr();
}
unique_ptr
源代码
//
// Created by zxzx on 2020/10/17.
//
#ifndef ZZZ_SMARTPTR_H
#define ZZZ_SMARTPTR_H
#include <iostream>
#include <memory>
#include <string>
using namespace std;
template <typename T> //首先,这是一个泛型类
class UniquePrt{
public: // 2个构造函数,一个普通指针的有参,一个拷贝构造函数,重载拷贝运算符,重载->,重载星号运算符。
UniquePrt(T* p= nullptr):m_p(p) {};
UniquePrt(const UniquePrt& sp) = delete;
UniquePrt& operator=(UniquePrt& p) = delete; // 改用reset命令,或者移动赋值
UniquePrt(const UniquePrt&& other):m_p(other.m_p){
other.m_p = nullptr;
};
UniquePrt<T>& operator=(UniquePrt<T>&& other){ // 转移赋值, 之前的是空的,所以不用处理。万一不是空的呢?这个系统会报异常的吗?
cout << "move assign" << endl;
swap(other); // other现在保存的是原来的指针,等离开命名空间,就会自动被析构,这块不用担心,这里我们也不用自己再释放了
return *this; //
};
~UniquePrt(){
if(m_p) delete m_p;
cout << "deconstructor" << endl;
}
T* operator->() const{ // 箭头重载
cout << "-> reload" << endl;
return m_p;
};
T& operator*() const{ // 指针解析运算符重载
return *m_p;
}
T* release(){ // 释放指针并返回原来的指针
T* ret = m_p;
m_p = nullptr; //
return ret;
}
void reset(T* p = nullptr){ // 释放以前的指针,换一个新的
T* old = m_p;
if(p != m_p){
if(m_p) delete m_p;
}
m_p = p;
}
void swap(UniquePrt& p){
using std::swap;
swap(m_p, p.m_p); // 注意交换指针属性,而不是别的
}
explicit operator bool() const noexcept {
return m_p != nullptr; // 这样就棒的多!!
}
private:
T* m_p;
};
#endif //ZZZ_SMARTPTR_H
测试代码
#include <memory>
#include <iostream>
#include "UniquePtr.h"
using namespace std;
class A{
public:
~A(){
cout <<"kill A\n";
}
};
// 如果程序执行过程中抛出了异常,unique_ptr就会释放它所指向的对象
// 传统的new 则不行
unique_ptr<A> fun1()
{
unique_ptr<A> p(new A());
//do something
return p;
}
void fun2()
{ // unique_ptr具有移动语义
unique_ptr<A> p = fun1();// 使用移动构造函数
// do something
}// 在函数退出的时候,p以及它所指向的对象都被删除释放
class Test{
public:
Test(){name = "";};
Test(string& i) {
name = i;
}
Test(string i) {
name = i;
}
Test(char* i) {
name = i;
}
~Test(){}
void showName(){
cout << name << endl;
}
string& getName(){
return name;
}
private:
string name;
};
void test_ptr(){
UniquePrt<Test> p1(new Test("abcd"));
p1->showName();
// UniquePrt<Test> p2;
// p2 = UniquePrt<Test>(new Test("zzz"));
p1.reset(new Test("124"));
p1->showName();
Test* t = p1.release();
cout << "t:" << t->getName() << endl;
// 这里做了一个很顽皮的实验,如果把 unique_ptr指向的元素取出来,然后获取它的指针,然后让另外一个unique_ptr接管,那么就出问题了
// p1.reset( &(*p2)); // 报错了pointer being freed was not allocated
// UniquePrt<Test> p3 (new Test("xxx"));
}
int main(){
test_ptr();
}
参考
[1]手动实现一个 unique_ptr
https://www.jianshu.com/p/77c2988be336