1.例外
1.1。例外プログラミングモデルと基本的な使用法
- 上記のモデルを説明するためにそれを使用する必要があります
double Div(int a, int b) {
if (b == 0) throw "Zero Div";//抛出一个字符串常量
cout << "要是异常抛出, 自我及其一下全部腰斩, 不会执行" << endl;
return (double)a / (double)b;
}
int main() {
try {
cout << Div(4, 0) << endl;
}
catch (int errid) {
//捕获错误码整形进行处理
cout << "错误编号: " << errid << endl;
}
catch (const char* msg) {
cout << "错误信息" << msg << endl;
}
cout << "异常处理结束了, 继续向后执行呀, 除非异常处理进行了中断" << endl;
return 0;
}
- 分析:例外がスローされるため、シーケンス後のコードは実行されなくなります
- 例外処理が終了した後、プロセスが終了しない限り、例外が処理された後、次のステートメントを実行し続けます。
1.2。カスタム例外クラス
class MyException {
public:
MyException(int errid, string errmsg)
: _errid(errid)
, _errmsg(errmsg)
{}
const string& what() const noexcept {
return _errmsg;
}
int GetErrid() const noexcept {
return _errid;
}
private:
int _errid;
string _errmsg;
};
1.3。例外処理関数の検索プロセス(関数スタックに沿って検索)
class MyException {
public:
MyException(int errid, string errmsg)
: _errid(errid)
, _errmsg(errmsg)
{}
const string& what() const noexcept {
return _errmsg;
}
int GetErrid() const noexcept {
return _errid;
}
private:
int _errid;
string _errmsg;
};
void h() {
throw MyException(0, "沿着函数调用方向往回找异常处理函数");
}
void g() {
try {
h();
}
catch (int errid) {
cout << "g()错误码是: " << errid << endl;
}
}
void f() {
try{
g();
}
catch (const runtime_error& re) {
cout << "f函数中处理函数: " << re.what() << endl;
}
}
int main() {
try {
f();
}
catch (const MyException& e) {
cout << "主函数中处理函数: " << e.what() << endl;
}
catch (...) {// 一般为了异常有个处理会在最后加上他
cout << "主函数中处理函数: " << "捕获到未知异常" << endl;
}
return 0;
}
- その結果、もちろん、メイン関数の対応する処理関数が関数スタックに沿って見つかります。
1.4.例外の再スロー、マルチキャッチ処理(より多くの外部処理)
1.4.1メモリリークを防ぐための例外処理
class Test {
public:
Test() {
//default ctor
}
~Test() {
cout << "dtor" << endl;
}
};
int main() {
try {
//Test t; 栈区对象肯定没问题的
Test* pt = new Test;//堆区呢?
throw 1;
delete pt; //会怎样??? 不会执行, 内存泄漏
}
catch (int errid) {
cout << "我会不会调用析构???" << endl;
cout << errid << endl;
}
return 0;
}
- その結果、当然、デストラクタは自動的に呼び出されません。何が問題なのですか????これは、メモリリークが発生することを意味します。
- したがって、メモリリークも、例外処理を作成するときに特別な注意が必要な問題です。
1.4.2例外マルチキャッチ処理、内側のレイヤーは処理を処理せず、外側にスローし続けます。
double Div(int a, int b) {
if (b == 0) {
throw MyException(0, "Zero Div");
}
return (double)a / (double)b;
}
void func() {
// 这里可以看到如果发生除0错误抛出异常,另外下面的array没有得到释放。
// 所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再
// 重新抛出去。
int* arr = new int[4]{ 0 };
try{
int a, b;
cout << "请输入被除数和除数: " << endl;
cin >> a >> b;
cout << Div(a, b) << endl;
}
catch (...) { //我仅仅处理内存, 至于信息等等继续抛出去其他函数处理
cout << "delete[]" << endl;
delete[] arr;
throw;//继续往外抛出
}
}
int main() {
try {
func();
}
catch (const MyException& e) {
cout << "错误信息: " << e.what() << endl;
}
return 0;
}
1.5.例外クラスの継承(ポリモーフィズム、基本クラスオブジェクトはサブクラスオブジェクトを参照)
- 最初の質問をしますか??なぜあなたはそれを継承したいのですか、あなたはそれを自分で書くことができませんか?
まず、自分で書くことは不可能ではありませんが、シーンは適していません。誰もが例外クラスを書いているので嬉しいですが、対応する処理関数を呼び出す必要がある場合は、クラスを再変更する必要がありますか?対応する処理関数を呼び出すためにあらゆる場所で名前を付けますか? ポリモーフィズムをどこに設定しますか?
基本クラスを使用してサブクラスオブジェクトを参照することは可能ですか。したがって、インターフェイスを呼び出すときは、基本クラスを使用して対応する処理関数を呼び出すだけで済み、基本クラスを継承するだけで必要な処理を書き換えることができます。機能.....。
ポリモーフィズムの定義を確認します。異なるオブジェクトを渡し、同じ関数を呼び出すと、異なる効果があります。実際、サブクラスは基本クラスの仮想関数を書き換えます。
ですから、実際、企業には一般的に独自の例外処理メカニズムがあり、独自の例外処理クラスには標準があります。それらを使用すると、状況に応じて仮想関数を継承および書き換えることができます。
- 例:手で書くだけ
class MyException {
public:
MyException(int errid, string errmsg)
: _errid(errid)
, _errmsg(errmsg)
{}
virtual string what() const noexcept {
return _errmsg;
}
virtual int GetErrid() const noexcept {
return _errid;
}
protected:
int _errid;
string _errmsg;
};
//继承的子类
class SqlException : public MyException {
public:
SqlException(int errid = 0, const char* msg = "")
: MyException(errid, msg)
{}
virtual string what() const noexcept {
string tmp("SqlException: ");
tmp += _errmsg;
return tmp;
}
};
class CacheException : public MyException {
public:
CacheException(int errid = 0, const char* msg = "")
: MyException(errid, msg)
{}
virtual string what() const noexcept {
string tmp("CacheException: ");
tmp += _errmsg;
return tmp;
}
};
class HttpServerException : public MyException {
public:
HttpServerException(int errid = 0, const char* msg = "")
: MyException(errid, msg)
{}
virtual string what() const noexcept {
string tmp("HttpServerException: ");
tmp += _errmsg;
return tmp;
}
};
2.いくつかのアセンブリ命令とレジスタを導入し、関数呼び出しのアセンブリプロセスを分析します
2.1。レジスタアセンブリ命令の基本
- epi:次の命令のアドレスを格納する命令レジスタ
- espとebpはどちらもポインタレジスタです
- esp:スタックトップレジスタ、関数スタックスタックの最上位を指す、スタックポインタ
- ebp:スタック最下位レジスタ、スタック最下位を指す、フレームポインタ
- プッシュ:データを関数スタックにプッシュし、espを変更します
- pop:関数スタックからのデータ、espを変更
- sub:減算演算
- 追加:加算演算
- 呼び出し:関数呼び出し
- ジャンプ:呼び出し元の関数に入る
- ret:関数呼び出しが終了した後のリターンアドレスで、外部呼び出し関数に戻ります
- 移動:データ転送、スタックの上下の変更
2.2。関数呼び出しスタックのアセンブリ命令の分析(VS2019)
- スタックへのパラメーターの逆プッシュ
- callは関数を呼び出し、入力時にretアドレスを自動的にプッシュします(呼び出された関数に入ります)
- 関数呼び出しの終了時にretの前にプッシュされたretアドレス(呼び出し元の関数に戻る)
- パラメータをクリーンアップするには、処理メカニズムを参照してください。一部は自分でクリーンアップする関数と呼ばれ、一部はクリーンアップする関数と呼ばれます。
3.浅いものから深いものへのスマートポインタの実現
3.1スマートポインタRAIIテクノロジの概要
- まず最初に、スマートポインターが必要な理由を理解しましょう。スマートポインターは、ポインターが指すリソース回復と使用状況管理のためのクラスです。
- メモリリーク:メモリリークとは何ですか?メモリリークとは、メモリの一部の制御を失ったが、制御を失う前に解放しないことを意味します。オペレーティングシステムは、ヒープからプロセスにメモリを割り当てます。積極的に削除しないと、プロセスの実行中にオペレーティングシステムが他のプロセスに割り当てることができなくなり、最初にこのメモリを割り当てた所有者が使用後に削除しないため、実行する方法がありません。このメモリが割り当てられているため、メモリリークに相当します
- メモリリークの危険性:メモリリークは、オペレーティングシステム、バックグラウンドサービスなどの実行時間の長いプログラムで発生し、メモリリークは応答をどんどん遅くし、最終的にはフリーズします。
- RAIIのアイデア: RAII(Resource Acquisition Is Initialization)は、オブジェクトのライフサイクルを使用して、プログラムリソース(メモリ、ファイルハンドル、ネットワーク接続、ミューテックスなど)を制御する単純なテクノロジです。
- そのため、スマートポインタが登場しました。スマートポインタによって定義されるオブジェクトのライフサイクルは制限されています。スマートポインタはスタックオブジェクトとして定義されます。関数が終了すると、デストラクタが自動的に呼び出され、リソースが自然に解放されます。 Deadlock unique_lockここでのスマートポインターはアイデアです)
3.2スマートポインタの最も基本的なフレームワークモデル
構築、デストラクタ、基本的なポインタ操作があってもOKです。これは一般的なフレームワークモデルであり、すべてを持っている必要はありません。
template<class T >
class SmartPtr {
public:
SmartPtr(T* ptr)
: _ptr(ptr)
{}
~SmartPtr() {
if (_ptr)
delete _ptr;
}
T& operator() {
return *_ptr;
}
T* operator() {
return _ptr;
}
private:
T* _ptr;
};
3.34種類のスマートポインタ特性の分析
- auto_ptr: 4種類のスマートポインターの1つで、問題はコピー構造にあり、割り当て後にポインターがぶら下がっています。ぶら下がっているポインターで操作を実行すると、エラーが報告されます。
- unique_ptr:自動コピーと割り当て後のポインターのぶら下がりの問題を解決するために、コピーの作成と割り当てのオーバーロードは直接禁止されています。
- shared_ptr:自動コピーと割り当て後のポインターのぶら下がりの問題は解決しますが、コピーと割り当てのオーバーロードを禁止することによって実装されるのではなく、参照テクノロジーと呼ばれる方法を使用して、割り当てまたはコピーに関係なく、ポインターのぶら下がりを回避します。参照カウント+1。これにより、コピー後、コピーへの転送によって元のポインタがぶら下がることはありませんが、コピーと同じアドレスと同じメモリリソースを共有します。
- 注:関数の再入可能性の問題を回避するには、共有リソースの操作を保護する必要があります。相互排除ロックは、一度に1つのスレッドのみが共有クリティカルリソースに書き込むようにするために使用されます。したがって、参照カウントの+と-すべての操作でロック保護を使用する必要があります
- weak_ptr:循環参照、循環参照の問題を解決するために、つまり、相互に参照カウント関係があり、相互解放の実際の削除は制限されています。。。
- 循環参照を解決するshared_ptrの原則:参照がカウントされるときに、_preおよび_nextポインターをweak_ptrスマートポインターに変更します。
- 原則は、node1-> _ next=node2;およびnode2->_prev = node1の場合、weak_ptrの_nextおよび_prevはnode1およびnode2の参照カウントを増加させないということです。
struct ListNode
{
int _data;
weak_ptr<ListNode> _prev;
weak_ptr<ListNode> _next;
~ListNode() { cout << "~ListNode()" << endl; }
};
3.43つのスマートポインターシミュレーション実装コード
auto_ptr
namespace tyj {
template<class T>
class auto_ptr {
public:
auto_ptr(T* ptr)
: _ptr(ptr)
{}
auto_ptr(auto_ptr<T>& ap) {
_ptr = ap._ptr;//转移资源
ap._ptr = nullptr;//原指针悬空
}
auto_ptr<T>& operator=(auto_ptr<T>& ap) {
if (this != &ap) {
if (_ptr)
delete _ptr;//清理现有资源
_ptr = ap._ptr;
ap._ptr = nullptr;
}
}
~auto_ptr() {
if (_ptr)
delete _ptr;
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
T* _ptr;
};
}
int main() {
int* pint = new int[4]{ 0 };
tyj::auto_ptr<int> smartp(pint);
*smartp = 1;
cout << *smartp << endl;
tyj::auto_ptr<int> smartp2(smartp);
//*smartp = 1;
//cout << *smartp << endl;
//smartp 不可以再进行写入了, 已经悬空了
return 0;
}
unique_ptr:コピーの作成と割り当てのオーバーロードを直接禁止する
namespace tyj {
template<class T>
class 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:
T* _ptr;
unique_ptr(unique_ptr<T>& up) = delete; //禁止掉构造函数
unique_ptr<T>& operator=(unique_ptr<T>& up) = delete;//禁止掉复制重载
};
}
shared_ptr:参照カウントの使用方法:参照カウントは重要なリソースであるため、その操作はmutexによって保護され、アトミック操作を実行する必要があります。。。(重要なリソースを保護する)
namespace tyj {
template <class T>
class shared_ptr {
public:
shared_ptr(T* ptr = nullptr)
: _ptr(ptr)
, _pmtx(new mutex)
, _pRefCount(new int(1))
{}
~shared_ptr() {
Release(); //释放资源
}
//增加引用计数, 赋值拷贝
shared_ptr(const shared_ptr<T>& sp)
: _ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pmtx(sp._pmtx) {
AddRefCount();//增加引用计数
}
shared_ptr<T>& operator=(shared_ptr<T>& sp) {
if (this != &sp) {
Release();//先释放可能持有的资源
_ptr = sp._ptr;
_pmtx = sp._pmtx;
_pRefCount = sp._pRefCount;
AddRefCount();//增加引用计数
}
return *this;
}
int UseCount() {
return *_pRefCount;
}
T* Get() {
return _ptr;
}
T& operator*() {
return *_ptr;
}
T* operator->() {
return _ptr;
}
private:
void AddRefCount() {//增加引用计数
_pmtx->lock();
++(*_pRefCount);
_pmtx->unlock();
}
void Release() {//释放资源, 减去一次引用计数
bool deleteFlag = 0;//判断是否需要释放资源, 真正的delete
_pmtx->lock();
if (--(*_pRefCount) == 0) {
delete _ptr;
delete _pRefCount;
deleteFlag = 1;
}
_pmtx->unlock();
if (deleteFlag) {
delete _pmtx;//最后释放锁
}
}
private:
T* _ptr;//指向管理资源的指针
int* _pRefCount;//引用计数指针
mutex* _pmtx;//互斥锁
};
}
4.まとめ
- 本論文では、まず異常の基本モデルを紹介し、次に異常学習の焦点を提案する。
- 1. {protect code} catch(capture type){例外処理ロジックブロック}を試してください
- 2.例外クラスをカスタマイズしたり、標準クラスを継承してハンドラー関数や例外情報関数を書き直したりできます。
- 3.例外処理関数は、関数スタックに沿って検索され、関数呼び出しの反対方向に検索されます。呼び出された関数で処理関数が見つからない場合は、呼び出し元の関数に戻って処理を検索します。働き。
- 4.例外クラスを継承する本質的な理由:基本クラスを使用してすべての派生クラスオブジェクトを受け取り、同じ関数インターフェイスを呼び出して、異なる呼び出し結果と異なる処理メカニズムを実現するために.... ユーザーのインターフェイスとクラスの呼び出し(ユーザーは基本クラスのオブジェクト参照を使用して受信するだけで済み、関数を削除してもかまいません。渡された特定のオブジェクトについては、気にする必要はありません)
- 5.レジスタとアセンブリ命令の学習、esp:スタックレジスタの最上位ebp:スタックレジスタの最下位epi:命令レジスタなど。
- 6. 4種類のスマートポインター、スマートポインターの反復的な進化には次の理由があります。
- auto_ptrの問題は、コピーまたは割り当て後、本体ポインターがぶら下がることです。ポインターのぶら下がりの問題を解決するために、unique_ptrは、割り当てのオーバーロードとコピーの構築を直接禁止します。shared_ptrがぶら下がりを解決する方法は、参照カウントを追加することです。weak_ptrはshared_ptrのサイクルについて参照、相互参照、相互制限、およびリソースを完全に解放できないという問題が発生します。shared_ptrの基本原理と比較して、相互に周期的に参照するときに参照をカウントしない++によって実現されます。