网站icp备案号是如何编制的,wordpress此网页包含重定向循环,品牌网图片新闻2003年下一条文章,cms做淘宝客网站目录
智能指针
异常导致执行流乱跳
智能指针解决问题
auto_ptr
unique_ptr
sharded_ptr
weak_ptr 智能指针
由于C11引入异常之后#xff0c;执行流乱跳#xff0c;所以导致之前 malloc/new 的空间很容易没有被释放#xff0c;导致内存泄露问题。
所以这时候#x…目录
智能指针
异常导致执行流乱跳
智能指针解决问题
auto_ptr
unique_ptr
sharded_ptr
weak_ptr 智能指针
由于C11引入异常之后执行流乱跳所以导致之前 malloc/new 的空间很容易没有被释放导致内存泄露问题。
所以这时候我们就需要一个可以自动释放的对象来管理这些空间来让这些空间都能被释放不会发生内存泄露。
下面我们先看下异常如何会引起执行流乱跳的问题。
异常导致执行流乱跳
我们看下面一段代码下面我们在 main 函数中中调用一个 test 函数 test 函数中调用 new 开空间等还调用了 div 函数div 函数里面如果发现有 0 那么就抛出除0错误。
class A
{
public:A(int a 0):_a(a){cout 构造函数A(int a 0) endl;}
~A(){cout 析构函数~A() endl;}int get_a(){return _a;}private:int _a;
};
int Div(int a, int b)
{if (b 0){throw invalid_argument(除0错误);}return a / b;
}
void test1()
{A* APtr new A;int a 0, b 0;cont 输入;cin a b;Div(a, b);
delete APtr;
}
int main()
{try{test1();}catch (exception e){cout e.what() endl;}return 0;
} 这个test函数里面我们在 delete APtr 之前调用 div 函数。 如果有除0错误那么就会直接跳到 main 函数里面不会进行释放 A 类型的指针所以就发生了内存泄露问题。
结果
构造函数A(int a 0)
输入1 0
除0错误 这里并没有调用析构函数
如果没有除0的话那么还是会正常释放的
构造函数A(int a 0)
输入1 1
析构函数~A() 所以如果是我们自己 mallo/new 的指针的话那么还是容易导致内存泄露。 那么怎么解决呢我们可以在 test 里面捕获一下异常然后再里面进行释放内存然后再把错误抛出去
void test1()
{A* APtr new A;cout 输入;int a 0, b 0;cin a b;try{Div(a, b);}catch (...){delete APtr;// 释放throw;}delete APtr;
} 如果是这样子的话那么就可以解决了。
结果
构造函数A(int a 0)
输入1 0
析构函数~A()
除0错误
但是上面这种情况还是比较简单的情况那么如果复杂一点呢
void test2()
{A* APtr1 new A(10); // 这里可能会抛异常A* APtr2 new A(20); // 这里可能会抛异常
div(1, 0); // 这里可能会抛异常
delete APtr1;delete APtr2;
} 这里三处可能会抛异常那么怎么办呢 如果是第一处抛异常那么还好没有内存泄露。 如果是第二处抛异常那么我们还需要释放第一处开的空间。 如果是第三处抛异常我们又需要释放第一处和第二处开的空间而且如果又更多的动态内存的申请呢 所以这样就很容易导致程序出现问题 那么我们可以怎么解决
智能指针解决问题 首先我们先说一下什么是智能指针。 当我们需要对 malloc/new 的空间进行管理时我们可不可以让一个对象去管理这段空间。 当这个对象构造时我们把空间交给这个对象。 当该对象析构时然后让该对象释放掉这段空间。 所以指针指针也是一个对象。
auto_ptr
auto_ptr 是一个智能指针我们下面先看一下如何实现可以让该对象释放时可以释放动态开辟的空间。
首先我们可以再该对象的构造函数里面用一个指针去构造而该对象也就是管理的这段空间
templateclass Tclass auto_ptr{public:// 构造函数 用一个指针初始化auto_ptr(T* ptr nullptr):_ptr(ptr){}// 析构函数 释放掉管理的空间~auto_ptr(){if (_ptr){delete _ptr;}}private:T* _ptr;};
那么我们使用 auto_ptr 看一下是否会又内存泄露
void test3()
{lxy::auto_ptrA Aptr1 new A(10);lxy::auto_ptrA Aptr2 new A(20);
Div(1, 0);// 除0错误会抛异常
}
结果
构造函数A(int a 0)
构造函数A(int a 0)
析构函数~A()
析构函数~A()
除0错误 这里看到并没有内存泄露。 而且其实这里我们也并没有手动的释放开辟的空间我们只是让 auto_ptr 进行管理了。 但是如果我们想要访问A对象里面的内容呢 所以我们的智能指针还需要做到像指针一样。 也就是重载 * 和 - T operator*(){return *_ptr;}T* operator-(){return _ptr;}
测试代码
void test3()
{lxy::auto_ptrA Aptr1 new A(10);lxy::auto_ptrA Aptr2 new A(20);cout Aptr1-get_a() endl;cout (*Aptr2).get_a() endl;
}
结果
构造函数A(int a 0)
构造函数A(int a 0)
10
20
析构函数~A()
析构函数~A()
这里就是没问题的那么如果我们想拷贝一下呢
我们试一下
void test4()
{lxy::auto_ptrA Aptr1 new A(10);lxy::auto_ptrA Aptr2(Aptr1);
} 上面这段代码用 Aptr1 拷贝 Aptr2 这里是浅拷贝所以auto_ptr 里面的成员变量是指针类型发生值拷贝 所以此时的Aptr1 和 Aptr2 里面的指针指向同一块空间。 然后等出作用域的时候两个对象都会释放然后就会导致一块空间被释放多次。
结果
构造函数A(int a 0)
析构函数~A()
析构函数~A() 上面就奔溃掉了~
那么怎么解决呢我们可以采用管理资源转移的方法来实现
也就是当我们发生拷贝时我们就可以让管理的内容从a对象转移到b对象 ~auto_ptr(){if (_ptr){delete _ptr;}}
这里我们再看上面的那一段代码就没问题了
构造函数A(int a 0)
析构函数~A() 正常释放。 但是当我们引入资源转移之后其他的问题又来了就是悬空 当我们现在再使用 Aptr1 对象访问里面的内容时就会报错 所以有人使用这个的话那么又是一个严重的问题。
// 这里为了方便测试我们将 A 类的成员变量公有了...
void test4()
{lxy::auto_ptrA Aptr1 new A(10);lxy::auto_ptrA Aptr2(Aptr1);(*Aptr1)._a;(*Aptr2)._a;
}
结果
构造函数A(int a 0) 上面对空指针解引用了会直接导致进程奔溃掉。
那么这个问题如何解决呢
我们看下面的其他类型的智能指针
unique_ptr 该智能指针解决上面指针悬空的方法就是直接不允许拷贝。 templateclass Tclass unique_ptr{public:unique_ptr(T* ptr nullptr):_ptr(ptr){}~unique_ptr(){if (_ptr){delete _ptr;}}T operator*(){return *_ptr;}T* operator-(){return _ptr;}private:unique_ptr(unique_ptr ptr) {};unique_ptr operator(unique_ptr ptr) {};private:T* _ptr;}; 上面实现不允许拷贝的方法就是将拷贝构造和赋值重载都只声明不实现 但是只声明不实现可能会有人再类外面实现所以为了避免这样做可以声明为私有。 其实还有一种解决方法那么就是关键字 delete delete 表示删除掉该函数。 unique_ptr(unique_ptr ptr) delete;unique_ptr operator(unique_ptr ptr) delete; 这就是将一个函数删除 删除掉的函数就不能被调用。
下面我们测试一下
void test5()
{lxy::unique_ptrA Aptr1 new A(10);lxy::unique_ptrA Aptr2(Aptr1);
}
这里是不能调用的我们可以看一下报错信息
“lxy::unique_ptrA::unique_ptr(lxy::unique_ptrA )”: 尝试引用已删除的函数 但是这里还是有问题那么如果我们需要拷贝呢
所以仅仅是这和 unique_ptr 还是不够的。
我们还是需要可以拷贝的智能指针我们来看下面
sharded_ptr shared_ptr 是可以拷贝的那么如何实现可以拷贝呢 如果只是单纯的拷贝过去那么就是值拷贝可能会导致多次释放。 那么我们可以使用引用计数的方法来表示该空间一共被多少个对象所管理。 那么这时候我们又出现问题了我们怎么实现引用计数呢 templateclass Tclass shared_ptr{public:private:T* _ptr;int _count;}; 上面这样实现可以吗 不可以 因为如果是成员变量的话那么 count 是每一个对象都又一个的那么就是无法起到引用计数的作用。 我们需要的是可以多个对象来管理一块空间的引用计数而不是每一个对象都有一个引用计数 那么我们怎么办呢 我们再看下面的这种方法 templateclass Tclass shared_ptr{public:private:T* _ptr;static int _count;}; 我们使用 static 的变量可以吗 如果我们使用 static 的变量那么该变量是属于整个类的。 同时也是属于该类的所有成员的那么如果现在有两个管理不同空间的对象那么此时的引用计数应该是多少呢 所以此时也是不可以的 那么我们应该怎么办 我们可以让引用计数是一个动态的也就是引用计数可以也是一个指针让管理相同空间的对象的引用计数也是相同的 templateclass Tclass shared_ptr{public:private:T* _ptr;int* _pcount;}; 所以此时我们只需要再构造函数的时候对这两个变量初始化就可以了。
构造函数 shared_ptr(T* ptr nullptr):_ptr(ptr), _pcount(new int(1)){}
析构函数 ~shared_ptr(){if (--(*_pcount) 0){delete _ptr;delete _pcount;}}
运算符重载 T operator*(){return *_ptr;}T* operator-(){return _ptr;}
拷贝构造
拷贝构造就是将成员变量的两个指针拷贝给其他的类。
拷贝结束后对引用计数进行加加。 shared_ptr(shared_ptr ptr):_ptr(ptr._ptr),_pcount(ptr._pcount){(*_pcount);}
赋值重载
赋值重载这里需要考虑一些问题 自己给自己赋值怎么办 被拷贝对象应该怎么办 拷贝对象应该怎么办 shared_ptr operator(shared_ptr ptr){// 判断是否是自己给自己赋值if (_ptr ptr._ptr)return *this;// 判断是否需要销毁空间if (--(*_pcount) 0){delete _ptr;delete _pcount;}_ptr ptr._ptr;_pcount ptr._pcount;(*_pcount);}
测试代码
void test6()
{lxy::shared_ptrA Aptr1(new A(20));lxy::shared_ptrA Aptr2(Aptr1);lxy::shared_ptrA Aptr3;Aptr3 Aptr2;lxy::shared_ptrA Aptr4(new A(100));lxy::shared_ptrA Aptr5(Aptr4);
}
结果
构造函数A(int a 0)
构造函数A(int a 0)
析构函数~A()
析构函数~A() 上面这里无论是拷贝还是赋值都没有问题。 实际上 shared_ptr 再一般情况下都是没有问题的但是再特殊情况下还是会有问题
我们来看下面的代码
struct Node
{A _a;lxy::shared_ptrNode next;lxy::shared_ptrNode prev;
};
void test7()
{lxy::shared_ptrNode n1(new Node);lxy::shared_ptrNode n2(new Node);
}
首先我们看 test7 函数这样是没有问题的
构造函数A(int a 0)
构造函数A(int a 0)
析构函数~A()
析构函数~A() 这里我们是可以正确的释放的。 我们下面让 n1 的 next 指向 n2 n2 的 prev 指向 n1。
void test7()
{lxy::shared_ptrNode n1(new Node);lxy::shared_ptrNode n2(new Node);n1-next n2;n2-prev n1;
}
结果
构造函数A(int a 0)
构造函数A(int a 0) 这里我们只调用到了构造函数并没有析构为什么 下面我们只让 n1 的 next 指向 n2试一下看有没有问题
void test7()
{lxy::shared_ptrNode n1(new Node);lxy::shared_ptrNode n2(new Node);n1-next n2;
}
结果
构造函数A(int a 0)
构造函数A(int a 0)
析构函数~A()
析构函数~A() 这里看到只让 n1 的 next 指向 n2 是没有问题的那么下面让 n2 的 prev 指向 n1 看有没有问题
void test7()
{lxy::shared_ptrNode n1(new Node);lxy::shared_ptrNode n2(new Node);n2-prev n1;
}
结果
构造函数A(int a 0)
构造函数A(int a 0)
析构函数~A()
析构函数~A() 这里看到我们让 n2 的 prev 指向 n1 也是没有问题的。 说明如果我们只是单个指向的话是不会出现刚才的内存泄露的问题的。 如果有互相指向那么就是会出现问题 下面分析一下为什么
我们来分析一下互相指向的话释放是怎么释放的
struct Node
{A _a;lxy::shared_ptrNode next;lxy::shared_ptrNode prev;
};lxy::shared_ptrNode n1(new Node);
lxy::shared_ptrNode n2(new Node); n1 什么时候释放n1 这个节点由 n1 和 n2 的prev 共同管理所以如果 n1 释放的话那么就需要 n2 的 prev 释放了才释放。 那么n2 的 prev 什么时候释放当 n2 释放了才释放。 n2 什么时候释放n2 和 n1 的next 再共同管理着一块空间那么 n2 释放就需要 n1 的next 释放了才释放。 那么 n1 的 next 什么时候释放当 n1 释放了才释放。
这发现是一个循环一样的东西首先我们是 n1 什么时候释放而最后也牵扯到了当 n1 释放的时候才释放。
所以此时就是一个循环那么怎么解决呢
那么我们可以让 next 和 prev 不参与管理这块空间不久好了吗
所以我们可以写一个 weak_ptr该对象不参与管理只是为了解决这个问题。
weak_ptr 那么该对象如何实现呢 首先我们需要让 weak_ptr 的类型可以指向 shared_ptr 的类型所以我们需要一个 shared_ptr 的构造函数。 那么既然 weak_ptr 不参与管理所以当 shared_ptr 构造 weak_ptr 后引用计数也不能加加。
templateclass Tclass weak_ptr{public:weak_ptr(T* ptr nullptr):_ptr(ptr){}weak_ptr(shared_ptrT ptr):_ptr(ptr._ptr){}T operator*(){return *_ptr;}T* operator-(){return _ptr;}private:T* _ptr;}; 但是这里是不正确的因为再 shared_ptr 类型的 构造函数但是那里访问了 shared_ptr 的私有所以这里会报错 这里可以有两个解决方法 将 weak_ptr 设置为 shared_ptr 的友元。 为 shared_ptr 提供一个 get 函数。 下面我们采用第二种。 // shared_ptr::getPtr()T* getPtr(){return _ptr;}// weak_ptr::weak_ptr(shared_ptrT ptr)weak_ptr(shared_ptrT ptr):_ptr(ptr.getPtr()){}
这样就可以解决问题了下面我们再看一下那个测试的结果。
struct Node
{A _a;lxy::weak_ptrNode next;lxy::weak_ptrNode prev;
};void test7()
{lxy::shared_ptrNode n1(new Node);lxy::shared_ptrNode n2(new Node);n1-next n2;n2-prev n1;
}
结果
构造函数A(int a 0)
构造函数A(int a 0)
析构函数~A()
析构函数~A() 这时候就解决问题了。