异常和智能指针
- 一.异常
- 1.C/C++的错误:
- 1.C
- 2.C++
- 3.三个关键字:
- 2.使用异常:
- 1.基本概念:
- 2.基本代码:
- 1.基本使用:
- 2.多个捕获:
- 3.任意类型异常的捕获:
- 4.异常的重新抛出:
- 3.异常安全:
- 1.基本概念
- 2.构造异常:
- 3.析构异常:
- 4.RAII的处理思路?
- 5.异常规范:
- 1.正常使用:
- 2.不正常的使用?
- 4.自定义的异常体系
- 5.C++标准库的异常体系:
- 二.智能指针
- 1.为什么需要智能指针?
- 1.简单代码:
- 2.内存泄漏:
- 2.智能指针
- 1.简单的模拟:
- 2.auto_ptr
- 1.简单使用:
- 2.模拟实现auto_ptr:
- 3.unique_ptr
- 4.shared_ptr
- 5.weak_ptr
一.异常
1.C/C++的错误:
1.C
1.终止当前的程序:assert断言,exit退出,导致程序无法运行完毕直接的退出。
2.返回错误码:查找这个错误码在库中对应的是什么错误。
2.C++
1.异常是一种处理错误的方式,当一个函数发现出现了无法处理的情况就可以抛出一个异常,让函数或者函数的调用者去处理抛出的异常。
3.三个关键字:
1.throw:当出现异常的时候我们通过throw关键字去抛出一个异常。
2.catch:通过catch去捕获异常,可以在catch中去对捕获到的异常进行处理,可以存在多个catch进行捕获。
3.try:try中的代码就是有可能存在异常的代码,try后一般存在catch。
2.使用异常:
1.基本概念:
1.异常可以在任意一层函数调用链中去进行捕获。
2.抛出的异常类型必须使用捕获相同类型的catch去捕获。
3.抛出异常后会直接跳到捕获异常的地方去,中间的函数正常结束。
4.当一个异常被抛出但是到main函数都没有被捕获程序就会报错。
5.可以写任意多个捕获。
6.catch后的代码可以正常执行。
7.抛出异常后在调用链上使用最近并且捕捉类型匹配的catch。
8.抛出异常对象会生成异常对象的拷贝,捕获后异常对象会被释放。
9.catch(…)捕捉任意类型的对象。
2.基本代码:
1.基本使用:
#include<iostream>
using namespace std;
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
int main()
{
try
{
divide();
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
2.多个捕获:
#include<iostream>
using namespace std;
void pwd()
{
int a = 0;
cin >> a;
//偶数错误:我们不希望它是一个偶数!
if (a % 2 == 0)
throw 2;
cout << "a: " << a << endl;
}
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
if (b == 0)
throw "除0错误";
cout << a / b << endl;
try {
pwd();
}
catch (int x)
{
cout << "1: " << x << endl;
}
}
int main()
{
try
{
divide();
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
catch (int x)
{
cout << "2: " << x << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
3.任意类型异常的捕获:
#include<iostream>
#include<string>
using namespace std;
void pwd1()
{
int a = 0;
cin >> a;
//偶数错误:我们不希望它是一个偶数!
if (a % 2 == 0)
throw 2;
cout << "a: " << a << endl;
}
void pwd2()
{
int b = 0;
cin >> b;
//奇数错误:我们不希望它是一个奇数!
if (b % 2 != 0)
throw string("b字符是一个奇数!");
cout << "b: " << b << endl;
}
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
if (b == 0)
throw "除0错误";
cout << a / b << endl;
pwd1();
pwd2();
}
int main()
{
try
{
divide();
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
catch (int x)
{
cout << "2: " << x << endl;
}
catch (...)
{
cout << "unknow errmsg" << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
//1.没有增加任意类型的异常捕获:
//2.增加任意类型的异常捕获:
4.异常的重新抛出:
1.异常的重新抛出,往往是为了解决其他的问题,不是异常本身的问题我们只是通过中间的捕获去解决其他问题之后还会把未处理的异常进行重新抛出!
#include<iostream>
#include<string>
using namespace std;
void pwd1()
{
int a = 0;
cin >> a;
//偶数错误:我们不希望它是一个偶数!
if (a % 2 == 0)
throw 2;
cout << "a: " << a << endl;
}
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
try
{
pwd1();
}
catch (...)
{
//2.空间释放:
delete[] pa;
cout << "delete[] pa " << endl;
//3.异常的重新抛出:
throw;
}
}
int main()
{
try
{
divide();
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
catch (int x)
{
cout << "2: " << x << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
3.异常安全:
1.基本概念
1.异常会导致执行流的乱跳,产生异常安全的问题。
2.在构造函数的时候不要去抛出异常,导致对象不完整。
3.在析构函数的时候不要去抛出异常,导致内存泄漏,空间没有完全释放。
2.构造异常:
1.问题?
2.这个对象没有开辟完整,并且pa指针没有被释放,导致内存泄漏。
#include<iostream>
#include<string>
using namespace std;
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
class A {
public:
A(int n)
{
pa = new int(n);
divide();
pb = new int(n);
}
~A()
{
delete[] pa;
delete[] pb;
cout <<"delete[] pa" << "delete[] pb" << endl;
}
private:
int* pa;
int* pb;
};
int main()
{
try
{
A a(10);
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
3.析构异常:
1.问题?
2.对象空间开辟完整,但是pb没有被正常释放导致内存泄漏。
#include<iostream>
#include<string>
using namespace std;
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
class A {
public:
A(int n)
{
pa = new int(n);
pb = new int(n);
}
~A()
{
delete[] pa;
divide();
delete[] pb;
cout <<"delete[] pa" << "delete[] pb" << endl;
}
private:
int* pa;
int* pb;
};
int main()
{
try
{
A a(10);
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
4.RAII的处理思路?
C++中的异常经常会导致资源泄漏的问题,在new和delete中抛出了异常可能导致资源泄漏,lock和unlock中导致死锁的问题,C++经常通过RAII来解决问题。RAII在下面的智能指针中去说。
5.异常规范:
1.异常规范的目的是让函数的使用者知道函数可能会抛什么类型的异常。
2.函数后面写throw()表示函数不抛异常。
3.函数后面没有写如何东西表示函数可能会抛任意类型的异常。
4.C++11中新增noexcept在函数后增加表示函数不会抛异常。
1.正常使用:
#include<iostream>
#include<string>
using namespace std;
void divide() throw(const char*, int ,string)
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
int main()
{
try
{
divide();
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
catch (int msgerror)
{
cout << msgerror << endl;
}
catch (string msgerror)
{
cout << msgerror << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
2.不正常的使用?
//1.假设抛A,B类型的异常但是抛了C类型的异常
#include<iostream>
#include<string>
using namespace std;
void divide() throw(int, string)
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
int main()
{
try
{
divide();
}
catch (int msgerror)
{
cout << msgerror << endl;
}
catch (string msgerror)
{
cout << msgerror << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
//2.假设不抛异常,但是还是抛异常了?
#include<iostream>
#include<string>
using namespace std;
void divide() throw()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
int main()
{
try
{
divide();
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
//1.C++11新增noexcept有一定的作用!
#include<iostream>
#include<string>
using namespace std;
void divide() noexcept
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
int main()
{
try
{
divide();
}
catch (const char* msgerror)
{
cout << msgerror << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
4.自定义的异常体系
1.如何统一的去管理异常的抛出类型呢?
2.抛出派生类的异常可以使用基类去捕获。
#include<iostream>
#include<windows.h>
#include<string>
using namespace std;
void divide() noexcept
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
class except {
public:
except(int id, const char* error)
:_id(id)
,_msgerror(error)
{}
virtual string what()
{
return _msgerror;
}
~except()
{}
private:
int _id;
const char* _msgerror;
};
class sqlerro : public except {
public:
sqlerro(int id, const char* error, const char* sqlerr)
:except(id, error)
, _sqlerr(sqlerr)
{}
virtual string what()
{
string str = "sql message";
return str;
}
private:
const char* _sqlerr;
};
class Tcperro : public except {
public:
Tcperro(int id, const char* error, const char* Tcperr)
:except(id, error)
, _Tcperr(Tcperr)
{}
virtual string what()
{
string str = "TCP message";
return str;
}
private:
const char* _Tcperr;
};
void operator_sql(int& n)
{
if (n % 2 == 0)
throw sqlerro(2, "sql erro", "select * from user");
cout << "sql success" << endl;
}
void operator_Tcp(int& n)
{
if (n % 3 == 0)
throw Tcperro(2, "Tcp erro", "sock error");
cout << "Tcp success" << endl;
}
int main()
{
srand(time(NULL));
while (1)
{
Sleep(1000);
try
{
int n = rand() % 100;
//1.操作库:
operator_sql(n);
//2.操作网络:
operator_Tcp(n);
}
catch (except msgerror)
{
cout << msgerror.what() << endl;
}
}
cout << "-----------------------------" << endl;
return 0;
}
5.C++标准库的异常体系:
二.智能指针
1.为什么需要智能指针?
1.简单代码:
1.我们知道new函数本身就有可能抛异常,当空间出现问题的时候。
2.连续的空间开辟需要像下面的代码一样进行异常的处理才不会出现问题。
3.有没有什么好的方法去管理呢?
#include<iostream>
#include<string>
using namespace std;
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
class A {
public:
A(int n)
{
try
{
pa = new int(n);
try
{
pb = new int(n);
try
{
pc = new int(n);
}
catch (...)
{
delete[] pa;
delete[] pb;
}
}
catch (...)
{
delete[] pa;
}
}
}
~A()
{
delete[] pa;
delete[] pb;
delete[] pc;
cout << "delete[] pa" << "delete[] pb" << endl;
}
private:
int* pa;
int* pb;
int* pc;
};
int main()
{
try
{
A a(10);
}
catch (...)
{
cout << "new error" << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
2.内存泄漏:
C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一
块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
2.智能指针
1.简单的模拟:
RAII的原理:
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
1.不需要显式地释放资源。
2.采用这种方式,对象所需的资源在其生命期内始终保持有效
#include<iostream>
#include<string>
using namespace std;
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
template<class T>
class smart_ptr {
public:
smart_ptr(T* a)
:_ptr(a)
{}
~smart_ptr()
{
if (_ptr)
delete _ptr;
}
private:
T* _ptr;
};
class A {
public:
A(int n)
{
pa = new int(n);
pb = new int(n);
}
~A()
{
delete[] pa;
delete[] pb;
cout << "delete[] pa" << "delete[] pb" << endl;
}
private:
int* pa;
int* pb;
};
int main()
{
try
{
smart_ptr<A> pa = new A(10);
divide();
}
catch (...)
{
cout << "new error" << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
2.auto_ptr
1.简单使用:
#include<iostream>
#include<memory>
#include<string>
using namespace std;
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
void text()
{
auto_ptr<int> pa(new int(10));
auto_ptr<int> pb(new int(10));
divide();
}
int main()
{
try
{
text();
}
catch (...)
{
cout << "new error" << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
2.模拟实现auto_ptr:
auto_ptr的实现原理:管理权转移的思想,模拟实现了一份bit::auto_ptr来了解它的原理
using namespace std;
namespace sfpy {
template<class T>
class auto_ptr {
public:
//1.构造:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
//2.拷贝构造:
auto_ptr(auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
//管理权限转移?
sp._ptr = nullptr;
}
//3.赋值:
auto_ptr<T>& operator=(auto_ptr<T>& ap)
{
//检查自己给自己赋值的情况:
if (this != &ap)
{
//1.释放自己:
if (_ptr)
delete _ptr;
//2.管理权限转移?
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
//像指针一样:
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//析构:
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
cout << "delete _ptr" << endl;
}
}
private:
T* _ptr;
};
}
3.unique_ptr
简单粗暴的防止拷贝防止赋值:
auto_ptr非常容易的产生了多次析构的问题!
using namespace std;
namespace sfpy {
template<class T>
class unique_ptr {
public:
//1.构造:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
//2.拷贝构造:
unique_ptr(auto_ptr<T>& sp) = delete
//3.赋值:
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete
//像指针一样:
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//析构:
~unique_ptr()
{
if (_ptr)
{
delete _ptr;
cout << "delete _ptr" << endl;
}
}
private:
T* _ptr;
};
}
4.shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源.
- shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减1。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
#include<iostream>
#include<memory>
#include<string>
#include"shared_ptr.h"
using namespace std;
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
void text()
{
int* a = new int(10);
sfpy::shared_ptr<int> pa(a);
sfpy::shared_ptr<int> pb(pa);
pb = pa;
divide();
}
int main()
{
try
{
text();
}
catch (...)
{
cout << "new error" << endl;
}
cout << "-----------------------------" << endl;
return 0;
}
#pragma once
#include<iostream>
using namespace std;
namespace sfpy {
template<class T>
class shared_ptr {
public:
//1.构造:
shared_ptr(T* ptr)
:_ptr(ptr)
,_pcount(new int(1))
{}
//2.拷贝构造:
shared_ptr(shared_ptr<T>& sp)
:_ptr(sp._ptr)
,_pcount(sp._pcount)
{
(*_pcount)++;
}
//3.赋值:
shared_ptr<T>& operator=(shared_ptr<T>& ap)
{
//检查自己给自己赋值的情况:
if (this != &ap)
{
//1.原来的要减少:
realse();
//2.新的增加:
_ptr = ap._ptr;
_pcount = ap._pcount;
(*_pcount)++;
}
return *this;
}
//像指针一样:
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
void realse()
{
if (_ptr && *_pcount == 1)
{
delete _ptr;
delete _pcount;
cout << "delete _ptr" << "delete _pcount;" << endl;
}
else
{
(*_pcount)--;
}
}
//析构:
~shared_ptr()
{
realse();
}
private:
T* _ptr;
int* _pcount;
};
}
5.weak_ptr
1.产生上面问题的本质是ListNode使用了sharedptr导致引用计数不会减到0
2.所以两个节点都不能正常的释放。
3.把ListNode节点的智能指针改变为weak_ptr
#include<iostream>
#include"shared_ptr.h"
namespace sfpy {
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const sfpy::shared_ptr<T>& sp)
:_ptr(sp.get())
{}
weak_ptr<T>& operator=(const sfpy::shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
}
#include<iostream>
#include<memory>
#include<string>
#include"weak_ptr.h"
#include"shared_ptr.h"
using namespace std;
struct ListNode {
ListNode(int x)
:_prev(nullptr)
,_next(nullptr)
,_date(x)
{}
sfpy::weak_ptr<ListNode> _prev;
sfpy::weak_ptr<ListNode> _next;
int _date;
};
//struct ListNode {
// ListNode(int x)
// :_prev(nullptr)
// , _next(nullptr)
// , _date(x)
// {}
//
// sfpy::shared_ptr<ListNode> _prev;
// sfpy::shared_ptr<ListNode> _next;
// int _date;
//};
void divide()
{
int a = 0, b = 0;
cin >> a >> b;
//1.开辟空间:
int* pa = new int(10);
if (b == 0)
throw "除0错误";
cout << a / b << endl;
}
void text()
{
sfpy::shared_ptr<ListNode> pa(new ListNode(1));
sfpy::shared_ptr<ListNode> pb(new ListNode(2));
pa->_next = pb;
pb->_prev = pa;
divide();
}
int main()
{
try
{
text();
}
catch (...)
{
cout << "new error" << endl;
}
cout << "-----------------------------" << endl;
return 0;
}