cpp 对象 C++ 的每一个成员函数在 class 中声明,但是却不出现在每个对象中:
每一个非内联的成员函数,只会诞生一个函数实例
每个内联函数,会在其每一个使用者身上产生一个函数实例
C++ 的类就相当于一个数据结构体加上多个函数:
1 Class = data structure + code (methods)
This 指针 This 指针就是指向实例对象自己的指针
案例一:This 的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <string.h> using namespace std;class Base {public : void fun () { cout<<name<<endl; } char name[8 ]; }; class A : public Base{public : void foo () { strcpy (this ->name,"A" ); this ->fun (); } }; class B : public Base{public : void foo () { strcpy (this ->name,"B" ); this ->fun (); } }; int main (void ) { A *a = new A (); B *b = new B (); a->foo (); b->foo (); }
在调用类函数时,会将其 new 出来的堆内存当做第一个参数传入(相当于传入了该对象的数据结构体)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int __cdecl main (int argc, const char **argv, const char **envp) { A *v3; B *v4; A *a; B *b; v3 = (A *)operator new (8uLL ); *v3 = 0LL ; a = v3; v4 = (B *)operator new (8uLL ); *v4 = 0LL ; b = v4; A::foo(a); B::foo(b); return 0 ; }
使用 This 指针,可以快速操控本实例对象的各个成员:
1 2 3 4 5 void __cdecl A::foo (A *const this ) { *(_WORD *)this ->name = 65 ; Base::fun(this ); }
重载 C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为:
函数重载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> using namespace std;class Test {public : void print (int i) { cout<<"int:" <<i<<endl; } void print (double f) { cout<<"double:" <<f<<endl; } }; int main (void ) { Test *t = new Test; t->print (1 ); t->print (1.1 ); }
对于类函数来说,编译器会把 [类名称,函数名称,参数列表] 放入哈希函数转化为一个哈希值,并用这个哈希值来当做函数的名称:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 ; int __cdecl main (int argc, const char **argv, const char **envp) public mainmain proc near f = qword ptr -18 ht= qword ptr -8 ; __unwind { push rbp mov rbp, rsp sub rsp, 20 h mov edi, 1 ; unsigned __int64 call __Znwm ; operator new (ulong) mov [rbp+t], rax mov rax, [rbp+t] mov esi, 1 ; imov rdi, rax ; this call _ZN4Test5printEi ; Test::print(int ) mov rdx, cs:qword_2018 mov rax, [rbp+t] mov [rbp+f], rdx movsd xmm0, [rbp+f] ; f mov rdi, rax ; this call _ZN4Test5printEd ; Test::print(double ) mov eax, 0 leave retn ; } main endp
在汇编代码中,程序调用的函数已经确定
那么编译器可能是在语法分析时,就通过参数列表确定了应该调用的函数
运算符重载(内部):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> using namespace std;class Box { public : double getVolume (void ) { return length * breadth * height; } void setAll (double len,double bre,double hei) { length = len; breadth = bre; height = hei; } Box operator + (const Box& b){ Box box; box.length = this ->length + b.length; box.breadth = this ->breadth + b.breadth; box.height = this ->height + b.height; return box; } double length; double breadth; double height; }; int main () { Box Box1; Box Box2; Box Box3; double volume = 0.0 ; Box1.setAll (1.0 ,1.0 ,1.0 ); Box2.setAll (2.0 ,2.0 ,2.0 ); volume = Box1.getVolume (); volume = Box2.getVolume (); Box3 = Box1 + Box2; volume = Box3.getVolume (); return 0 ; }
在一个类中重载运算符过后,其作用范围为整个文件,以及引入该类的其他文件
本质上重载运算符就是调用对应的函数(其参数和返回值必须符合条件)
1 2 3 4 5 6 7 Box::setAll (&Box1, 1.0 , 1.0 , 1.0 ); Box::setAll (&Box2, 2.0 , 2.0 , 2.0 ); Box::getVolume (&Box1); volume = Box::getVolume (&Box2); Box::operator +(&v4, &Box1, &Box2); Box3 = v4; Box::getVolume (&Box3);
1 2 3 4 5 6 7 Box *__cdecl Box::operator +(Box *retstr, Box *const this , const Box *b) { retstr->length = b->length + this ->length; retstr->breadth = b->breadth + this ->breadth; retstr->height = b->height + this ->height; return retstr; }
PS:Cpp 库中也有许多运算符重载的案例:new
,<<
,>>
等
运算符重载(外部):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> using namespace std;class Box { public : double getVolume (void ) { return length * breadth * height; } void setAll (double len,double bre,double hei) { length = len; breadth = bre; height = hei; } double length; double breadth; double height; }; Box operator + (const Box& a,const Box& b){ Box box; box.length = a.length + b.length; box.breadth = a.breadth + b.breadth; box.height = a.height + b.height; return box; } int main () { Box Box1; Box Box2; Box Box3; double volume = 0.0 ; Box1.setAll (1.0 ,1.0 ,1.0 ); Box2.setAll (2.0 ,2.0 ,2.0 ); volume = Box1.getVolume (); volume = Box2.getVolume (); Box3 = Box1 + Box2; volume = Box3.getVolume (); return 0 ; }
在全局重载运算符过后,其作用范围就是全局(但可以被命名空间限制)
1 2 3 4 5 6 7 Box::setAll (&Box1, 1.0 , 1.0 , 1.0 ); Box::setAll (&Box2, 2.0 , 2.0 , 2.0 ); Box::getVolume (&Box1); volume = Box::getVolume (&Box2); operator +(&v4, &Box1, &Box2);Box3 = v4; Box::getVolume (&Box3);
1 2 3 4 5 6 7 Box *__cdecl operator +(Box *retstr, const Box *a, const Box *b) { retstr->length = b->length + a->length; retstr->breadth = b->breadth + a->breadth; retstr->height = b->height + a->height; return retstr; }
内联函数 在类的声明内部声明和定义的函数叫做内联成员函数
内联函数类似于宏函数,但内联函数是在编译时展开,而宏在预编译时展开
内联函数的定义和使用必须在同一文件,因此最好将内联函数定义放在头文件中
定义在类中的成员函数默认都是内联的,类外定义则要加上 inline(类的成员函数是指那些把定义和原型写在类定义内部的函数)
1 2 3 4 5 6 7 class Test { public : void setA (int _a) ; void setB (int _b) {b = _b;} inline void setC (int _c) ; int a,b,c; };
在 IDA 中分析或者在 GDB 中调试,都发现函数没有内联成功(还是当成普通文件来调用),可能是编译器的原因
构造函数 以下几种情况下,会合成有用的构造函数:
带有默认构造函数的成员对象(例如:string 类)
一个派生类的父类带有构造函数(或者父类的成员对象带有默认构造函数),那么子类:
如果没有定义构造函数,则会合成默认构造函数
如果有构造函数,但是没有调用父类的构造函数,则编译器会插入一些代码调用父类的默认构造函数
带有一个虚函数的类
类声明(或继承)一个虚函数
类派生自一个继承串链,其中有一个或更多的虚基类
案例一:带有默认构造函数的成员对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> #include <string> using namespace std; class Test { public : string name; }; int main (int argc, char * argv[]) { Test t; return 0 ; }
1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { Test t; unsigned __int64 v5; v5 = __readfsqword(0x28 u); Test::Test (&t); Test::~Test (&t); return 0 ; }
由于 Test 类中的 string 类带有默认构造函数,因此 Test 类会合成一个构造函数:
1 2 3 4 void __cdecl Test::Test (Test *const this ) { std::string::basic_string (this ); }
案例二:没有定义构造函数,则会合成默认构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <string> using namespace std; class Test1 {public : Test1 (); }; class Test2 : public Test1{}; Test1::Test1 (void ){ cout << "Test1" << endl; } int main (int argc, char * argv[]) { Test2 t; return 0 ; }
1 2 3 4 5 6 7 8 9 int __cdecl main (int argc, const char **argv, const char **envp) { Test2 t; unsigned __int64 v5; v5 = __readfsqword(0x28 u); Test2::Test2 (&t); return 0 ; }
由于 Test2 没有构造函数,但父类 Test1 有构造函数,因此在 Test2 的构造函数中会调用 Test1 的构造函数:
1 2 3 4 void __cdecl Test2::Test2 (Test2 *const this ) { Test1::Test1 (this ); }
案例三:如果有构造函数,但是没有调用父类的构造函数,则编译器会插入一些代码调用父类的默认构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <string> using namespace std; class Test1 {public : Test1 (); }; class Test2 : public Test1{public : Test2 (); }; Test1::Test1 (void ){ cout << "Test1" << endl; } Test2::Test2 (void ){ cout << "Test2" << endl; } int main (int argc, char * argv[]) { Test2 t; return 0 ; }
1 2 3 4 5 6 7 8 9 int __cdecl main (int argc, const char **argv, const char **envp) { Test2 t; unsigned __int64 v5; v5 = __readfsqword(0x28 u); Test2::Test2 (&t); return 0 ; }
由于在 Test2 的构造函数中没有调用父类 Test1 的构造函数,因此编译器会自动加上 Test2 的构造函数:
1 2 3 4 5 6 7 8 void __cdecl Test2::Test2 (Test2 *const this ) { __int64 v1; Test1::Test1 (this ); v1 = std::operator <<<std::char_traits<char >>(&std::cout, "Test2" ); std::ostream::operator <<(v1, &std::endl<char ,std::char_traits<char >>); }
案例四:带有一个虚函数的类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> using namespace std;class Test {public : virtual void foo () { cout<<"Test::foo() is called" <<endl; } }; int main (void ) { Test *t = new Test (); t->foo (); return 0 ; }
1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { Test *v3; v3 = (Test *)operator new (8uLL ); v3->_vptr_Test = 0LL ; Test::Test (v3); (*v3->_vptr_Test)(v3, argv); return 0 ; }
由于 Test 中有虚函数,因此编译器会自动为其生成构造函数:
1 2 3 4 void __cdecl Test::Test (Test *const this ) { this ->_vptr_Test = (int (**)(...))&off_3D70; }
虚函数 面向对象的语言有三大特性:继承、封装、多态,虚函数就是 cpp 实现多态的方式
案例一:使用虚函数实现多态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <iostream> using namespace std;class Base {public : virtual void foo () { cout<<"Base::foo() is called" <<endl; } }; class A :public Base{public : void foo () { cout<<"A::foo() is called" <<endl; } }; class B :public Base{public : void foo () { cout<<"B::foo() is called" <<endl; } }; class C :public Base{public : void foo () { cout<<"C::foo() is called" <<endl; } }; int main (void ) { Base *a = new B (); a->foo (); ((A *)a)->foo (); return 0 ; }
当使用类的指针调用成员函数时:
普通函数由指针类型决定
虚函数由指针指向的实际类型决定
1 2 3 4 5 6 7 8 9 10 11 int __cdecl main (int argc, const char **argv, const char **envp) { B *v3; v3 = (B *)operator new (8uLL ); v3->_vptr_Base = 0LL ; B::B (v3); (*v3->_vptr_Base)(v3, argv); (*v3->_vptr_Base)(v3); return 0 ; }
这个 _vptr_Base
就是虚指针基址,它将会在对应的构造函数中进行初始化
1 2 3 4 5 void __cdecl B::B (B *const this ) { Base::Base(this ); this ->_vptr_Base = (int (**)(...))&off_3D40; }
1 2 .data.rel.ro:0000000000003 D40 8 C 12 00 00 00 00 00 00 off_3D40 dq offset _ZN1B3fooEv ; DATA XREF: B::B(void )+18 ↑o .data.rel.ro:0000000000003 D40 ; B::foo(void )
对于拥有虚函数的类,其每个对象均具有一个指向本类虚函数表的指针 _vptr_Base
(可以将其理解为虚函数的函数指针)
案例二:虚函数的调用与虚表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> using namespace std;class Base {public : virtual void foo1 () { cout<<"Base::foo1() is called" <<endl; } virtual void foo2 () { cout<<"Base::foo2() is called" <<endl; } virtual void foo3 () { cout<<"Base::foo3() is called" <<endl; } virtual void foo4 () { cout<<"Base::foo4() is called" <<endl; } virtual void foo5 () { cout<<"Base::foo5() is called" <<endl; } }; int main (void ) { Base *a = new Base (); a->foo1 (); a->foo2 (); a->foo3 (); a->foo4 (); a->foo5 (); }
cpp 底层处理虚表的方式就是强转并调用 _vptr_Base + offset
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __cdecl main (int argc, const char **argv, const char **envp) { Base *v3; Base *a; v3 = (Base *)operator new (8uLL ); v3->_vptr_Base = 0LL ; Base::Base (v3); a = v3; (*v3->_vptr_Base)(v3, argv); (*((void (__fastcall **)(Base *))a->_vptr_Base + 1 ))(a); (*((void (__fastcall **)(Base *))a->_vptr_Base + 2 ))(a); (*((void (__fastcall **)(Base *))a->_vptr_Base + 3 ))(a); (*((void (__fastcall **)(Base *))a->_vptr_Base + 4 ))(a); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 __int64 __fastcall main (int a1, char **a2, char **a3) { _QWORD *v3; _QWORD *v5; v3 = (_QWORD *)operator new (8uLL ); *v3 = 0LL ; sub_13B4 (v3, a2); v5 = v3; (*(void (__fastcall **)(_QWORD *))*v3)(v3); (*(void (__fastcall **)(_QWORD *))(*v5 + 8LL ))(v5); (*(void (__fastcall **)(_QWORD *))(*v5 + 16LL ))(v5); (*(void (__fastcall **)(_QWORD *))(*v5 + 24LL ))(v5); (*(void (__fastcall **)(_QWORD *))(*v5 + 32LL ))(v5); return 0LL ; }
1 2 3 4 5 6 .data.rel.ro:0000000000003 D50 9 C 12 00 00 00 00 00 00 off_3D50 dq offset _ZN4Base4foo1Ev ; DATA XREF: Base::Base(void )+8 ↑o .data.rel.ro:0000000000003 D50 ; Base::foo1(void ) .data.rel.ro:0000000000003 D58 D4 12 00 00 00 00 00 00 dq offset _ZN4Base4foo2Ev ; Base::foo2(void ) .data.rel.ro:0000000000003 D60 0 C 13 00 00 00 00 00 00 dq offset _ZN4Base4foo3Ev ; Base::foo3(void ) .data.rel.ro:0000000000003 D68 44 13 00 00 00 00 00 00 dq offset _ZN4Base4foo4Ev ; Base::foo4(void ) .data.rel.ro:0000000000003 D70 7 C 13 00 00 00 00 00 00 dq offset _ZN4Base4foo5Ev ; Base::foo5(void )
1 2 3 4 5 6 pwndbg> telescope 0x55883ca88000 +0x3D50 00 :0000 │ rdx 0x55883ca8bd50 —▸ 0x55883ca8929c (Base::foo1()) ◂— push rbp01 :0008 │ 0x55883ca8bd58 —▸ 0x55883ca892d4 (Base::foo2()) ◂— push rbp02 :0010 │ 0x55883ca8bd60 —▸ 0x55883ca8930c (Base::foo3()) ◂— push rbp03 :0018 │ 0x55883ca8bd68 —▸ 0x55883ca89344 (Base::foo4()) ◂— push rbp04 :0020 │ 0x55883ca8bd70 —▸ 0x55883ca8937c (Base::foo5()) ◂— push rbp
案例三:虚函数的继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> using namespace std;class Base {public : virtual void foo1 () { cout<<"Base::foo1() is called" <<endl; } virtual void foo2 () { cout<<"Base::foo2() is called" <<endl; } virtual void foo3 () { cout<<"Base::foo3() is called" <<endl; } }; class A : public Base{public : virtual void fooa () { cout<<"Base::fooa() is called" <<endl; } virtual void foob () { cout<<"Base::fooa() is called" <<endl; } }; class B : public Base{public : virtual void fooa () { cout<<"Base::fooa() is called" <<endl; } virtual void foob () { cout<<"Base::fooa() is called" <<endl; } }; int main (void ) { A *a = new A (); B *b = new B (); a->foo1 (); a->foo2 (); b->fooa (); b->foob (); a->fooa (); a->foob (); }
当一个类继承带有虚函数的类时,它会先将父类的虚表复制一份,然后将自己的虚表添加到后面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int __cdecl main (int argc, const char **argv, const char **envp) { A *v3; B *v4; A *a; B *b; v3 = (A *)operator new (8uLL ); v3->_vptr_Base = 0LL ; A::A (v3); a = v3; v4 = (B *)operator new (8uLL ); v4->_vptr_Base = 0LL ; B::B (v4); b = v4; (*a->_vptr_Base)(a, argv); (*((void (__fastcall **)(A *))a->_vptr_Base + 1 ))(a); (*((void (__fastcall **)(B *))b->_vptr_Base + 3 ))(b); (*((void (__fastcall **)(B *))b->_vptr_Base + 4 ))(b); (*((void (__fastcall **)(A *))a->_vptr_Base + 3 ))(a); (*((void (__fastcall **)(A *))a->_vptr_Base + 4 ))(a); return 0 ; }
1 2 3 4 .data.rel.ro:0000000000003 D30 D4 12 00 00 00 00 00 00 off_3D30 dq offset _ZN4Base4foo1Ev ; DATA XREF: Base::Base(void )+8 ↑o .data.rel.ro:0000000000003 D30 ; Base::foo1(void ) .data.rel.ro:0000000000003 D38 0 C 13 00 00 00 00 00 00 dq offset _ZN4Base4foo2Ev ; Base::foo2(void ) .data.rel.ro:0000000000003 D40 44 13 00 00 00 00 00 00 dq offset _ZN4Base4foo3Ev ; Base::foo3(void )
1 2 3 4 5 6 .data.rel.ro:0000000000003 CF8 D4 12 00 00 00 00 00 00 off_3CF8 dq offset _ZN4Base4foo1Ev ; DATA XREF: A::A(void )+18 ↑o .data.rel.ro:0000000000003 CF8 ; Base::foo1(void ) .data.rel.ro:0000000000003 D00 0 C 13 00 00 00 00 00 00 dq offset _ZN4Base4foo2Ev ; Base::foo2(void ) .data.rel.ro:0000000000003 D08 44 13 00 00 00 00 00 00 dq offset _ZN4Base4foo3Ev ; Base::foo3(void ) .data.rel.ro:0000000000003 D10 7 C 13 00 00 00 00 00 00 dq offset _ZN1A4fooaEv ; A::fooa(void ) .data.rel.ro:0000000000003 D18 B4 13 00 00 00 00 00 00 dq offset _ZN1A4foobEv ; A::foob(void )
1 2 3 4 5 6 .data.rel.ro:0000000000003 CC0 D4 12 00 00 00 00 00 00 off_3CC0 dq offset _ZN4Base4foo1Ev ; DATA XREF: B::B(void )+18 ↑o .data.rel.ro:0000000000003 CC0 ; Base::foo1(void ) .data.rel.ro:0000000000003 CC8 0 C 13 00 00 00 00 00 00 dq offset _ZN4Base4foo2Ev ; Base::foo2(void ) .data.rel.ro:0000000000003 CD0 44 13 00 00 00 00 00 00 dq offset _ZN4Base4foo3Ev ; Base::foo3(void ) .data.rel.ro:0000000000003 CD8 EC 13 00 00 00 00 00 00 dq offset _ZN1B4fooaEv ; B::fooa(void ) .data.rel.ro:0000000000003 CE0 24 14 00 00 00 00 00 00 dq offset _ZN1B4foobEv ; B::foob(void )
调用约定 C/C++ 函数调用约定,主要是对以下两个方面进行了约定:
当参数个数多于一个时,按照什么顺序把参数压入堆栈(参数的入栈顺序)
函数调用后,由谁来把堆栈恢复原状
常见的调用方式有:
C 语言:__cdecl __stdcall __fastcall naked __pascal
C++ 语言:__cdecl __stdcall __fastcall naked __pascal __thiscall
下面就分别介绍这几种调用方式:
__stdcall
:StandardCall 的缩写,是C++的标准调用方式
使用 PASCAL 宏,WINAPI 宏和 CALLBACK 宏来指定函数的调用方式为 stdcall
1 int _stdcall function (int a, int b) ;
参数从右向左依次压入堆栈
由被调用函数自己来恢复堆栈,称为自动清栈
函数名自动加前导下划线,后面紧跟着一个@,其后紧跟着参数的大小
__cdecl
:C Declaration 的缩写,cdecl 调用方式又称为C调用方式,是32位C程序默认的调用方式
1 2 int function (int a, int b) int _cdecl function (int a, int b)
参数从右向左依次压入堆栈
由调用者恢复堆栈,称为手动清栈
函数名自动加前导下划线
__fastcall
:一种快速调用方式(通过 CPU 寄存器来传递参数),是64位C程序默认的调用方式
1 int fastcall function (int a, int b) ;
前6个参数分别放入 RDI,RSI,RDX,RCX,R8,R9
其余参数从右向左依次压入堆栈
如果需要用栈传参,则使用手动清栈
__thiscall
:唯一一个不能明确指明的函数修饰,因为 thiscall 不是关键字,是C++类成员函数默认的调用约定
由于成员函数调用还有一个 this 指针,因此必须特殊处理
this 指针将作为第一个参数传入,其余参数处理和 __cdecl/__thiscall
一样(取决于程序是32位还是64位)
__stdcall
和 __cdecl
的不同之处:
stdcall 方式是在函数返回时利用 retn x
指令清除栈中的参数
cdecl 方式是函数返回后,由调用函数者修改 esp 的值来清除栈中的参数
多重继承 一个派生类如果只继承一个基类,称作单继承,那么如果继承了多个基类,就称作多继承
1 class C :public A,public B{};
优点:派生类通过多重继承,可以得到多个基类的数据和方法,更大程度的实现了代码复用
缺点:可能会导致某个类被重复构造,可能得到重复的基类数据
案例:多重继承导致重复基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;class A {public : A (int data):ma (data){ cout << "A()" << endl; } ~A (){ cout << "~A()" << endl; } protected : int ma; }; class B :public A{public : B (int data):A (data),mb (data+1 ) { cout << "B()" << endl; } ~B (){ cout << "~B()" << endl; } protected : int mb; }; class C :public A{public : C (int data):A (data),mc (data+2 ) { cout << "C()" << endl; } ~C (){ cout << "~C()" << endl; } protected : int mc; }; class D :public B, public C{public : D (int data):B (data),C (data),md (data+3 ) { cout << "D()" << endl; } ~D (){ cout << "~D()" << endl; } protected : int md; }; int main () { D* a = new D (10 ); return 0 ; }
虚继承 在继承方式前面加上 virtual 关键字就是虚继承
1 class B :virtual public A{};
如果虚继承类拥有派生类,则构造虚基类的任务将会交给派生类完成
案例:虚继承解决重复基类的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;class A {public : A (int data):ma (data){ cout << "A()" << endl; } ~A (){ cout << "~A()" << endl; } protected : int ma; }; class B :virtual public A{public : B (int data):A (data),mb (data+1 ) { cout << "B()" << endl; } ~B (){ cout << "~B()" << endl; } protected : int mb; }; class C :virtual public A{public : C (int data):A (data),mc (data+2 ) { cout << "C()" << endl; } ~C (){ cout << "~C()" << endl; } protected : int mc; }; class D :public B, public C{public : D (int data):A (data),B (data),C (data),md (data+3 ) { cout << "D()" << endl; } ~D (){ cout << "~D()" << endl; } protected : int md; }; int main () { D* a = new D (10 ); return 0 ; }
PS:由于B虚继承A,因此派生类D需要完成虚基类A的构造
案例:虚基类的构造问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <iostream> using namespace std;class A {public : A (int data):ma (data){ cout << "A()" << endl; } ~A (){ cout << "~A()" << endl; } protected : int ma; }; class B :virtual public A{public : B (int data):A (data),mb (data+1 ) { cout << "B()" << endl; } ~B (){ cout << "~B()" << endl; } protected : int mb; }; class C :public B{public : C (int data):A (data),B (data),mc (data+2 ) { cout << "C()" << endl; } ~C (){ cout << "~C()" << endl; } protected : int mc; }; class D :public C{public : D (int data):A (data),C (data),md (data+3 ) { cout << "D()" << endl; } ~D (){ cout << "~D()" << endl; } protected : int md; }; int main () { D* a = new D (10 ); return 0 ; }
由于B虚继承A,因此派生类CD都有可能完成虚基类A的构造
通常由最后一级的派生类通常负责构造虚基类
虚基类的内存布局:
1 2 3 4 5 00 :0000 │ 0x55555556aea0 ◂— 0x0 01 :0008 │ 0x55555556aea8 ◂— 0x21 02 :0010 │ rbx 0x55555556aeb0 —▸ 0x555555557cb8 ◂— 0x555555557cb8 03 :0018 │ 0x55555556aeb8 ◂— 0xc0000000b 04 :0020 │ 0x55555556aec0 ◂— 0xa0000000d
虚继承的子类都有一个虚基类指针,其指向虚基类表(虚基类指针和虚函数的虚指针不是同一个东西)
虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现
1 2 3 4 5 6 7 8 pwndbg> telescope 0x55555556aea0 00 :0000 │ 0x55555556aea0 ◂— 0x0 01 :0008 │ 0x55555556aea8 ◂— 0x31 02 :0010 │ rbx 0x55555556aeb0 —▸ 0x555555557c28 ◂— 0x0 03 :0018 │ 0x55555556aeb8 ◂— 0xc0000000b 04 :0020 │ 0x55555556aec0 ◂— 0xd 05 :0028 │ 0x55555556aec8 —▸ 0x555555557c40 —▸ 0x55555555536c (A::fun()) ◂— endbr64 06 :0030 │ 0x55555556aed0 ◂— 0xa
这里的 0x555555557c40
就是A类的虚指针,而 0x555555557c28
是D类的虚基类指针
匿名函数 Lambda lambda 函数是一种匿名函数,它表示一个接受参数并返回一个值的函数
lambda 函数的语法如下:
1 [capture](parameters) -> return_type { function_body }
测试样例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h> typedef int (*lambda_ta) (int ) ;typedef int (*lambda_tb) (int ,int ) ;typedef double (*lambda_tc) (double ) ;lambda_ta lambdaA = [](int a) -> int { return a * 2 ; }; lambda_tb lambdaB = [](int a,int b) -> int { return a * b; }; int main () { int x0 = 5 ; int x1 = lambdaA (x0); int x2 = lambdaB (x0,x1); double y0 = x1 + x2; double y1 = [](double a) -> double { return a * 2 ; }(y0); return 0 ; }
1 2 3 4 x0 = 5 ; x1 = lambdaA (5 ); y0 = (double )(x1 + lambdaB (5 , x1)); main::{lambda (double )#1 }::operator ()(&__closure, y0);
匿名函数和普通函数本质上的区别就是:匿名函数不能通过函数名来调用
调用全局匿名函数其实是调用全局变量上对应的函数指针:
1 2 3 4 5 6 7 8 .data:0000000000004010 public lambdaA .data:0000000000004010 ; lambda_ta lambdaA .data:0000000000004010 1 A 12 00 00 00 00 00 00 lambdaA dq offset _ZN7lambdaAMUliE_4_FUNEi .data:0000000000004010 ; DATA XREF: main+22 ↑r .data:0000000000004018 public lambdaB .data:0000000000004018 ; lambda_tb lambdaB .data:0000000000004018 55 12 00 00 00 00 00 00 lambdaB dq offset _ZN7lambdaBMUliiE_4_FUNEii .data:0000000000004018 ; DATA XREF: main+33 ↑r
而局部匿名函数本质上是对 ()
的重载
调用局部匿名函数其实是调用对应的重载函数:
1 2 3 4 5 6 double __cdecl main::{lambda (double )#1 }::operator ()( const main::$256B 327EE357AF9FCD813216707B60D5 *const __closure, double a) { return a + a; }
泛型编程 模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码
模板有两类:函数模板,类模板
函数模板
不规定某个函数的传参类型或者返回值类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <string> using namespace std; template <class T > void Swap (T &num1, T& num2) { T tmp = num1; num1 = num2; num2 = tmp; } int main () { int a = 10 , b = 20 ; Swap (a,b); cout << "a :" << a << "b :" << b << endl; float c = 10.55f , d = 3.14f ; Swap (c, d); cout << "c :" << c << "d :" << d << endl; }
函数模板不规定参数类型,返回值类型
在编译时,编译器会根据实际传参来创建不同类型的副本,这个过程被称为函数模板实例化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 a = 10 ; b = 20 ; Swap<int >(&a, &b); v3 = std::operator <<<std::char_traits<char >>(&std::cout, &unk_2005); v4 = std::ostream::operator <<(v3, (unsigned int )a); v5 = std::operator <<<std::char_traits<char >>(v4, &unk_2009); v6 = std::ostream::operator <<(v5, (unsigned int )b); std::ostream::operator <<(v6, &std::endl<char ,std::char_traits<char >>); c = 10.55 ; d = 3.1400001 ; Swap<float >(&c, &d); v7 = std::operator <<<std::char_traits<char >>(&std::cout, &unk_200D); v8 = std::ostream::operator <<(v7, *(double *)_mm_cvtsi32_si128(LODWORD (c)).m128i_i64); v9 = std::operator <<<std::char_traits<char >>(v8, &unk_2011); v10 = std::ostream::operator <<(v9, *(double *)_mm_cvtsi32_si128(LODWORD (d)).m128i_i64); std::ostream::operator <<(v10, &std::endl<char ,std::char_traits<char >>);
下面是一个特殊的案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <cassert> using namespace std;int add (int a,int b) { return a+b; } typedef int (*lambda_t ) (int ,int ) ;lambda_t sub = [](int a,int b) -> int { return a-b; }; template <typename Fn>void func (Fn const &fn) { int a = 10 ; int b = 5 ; cout << fn (a,b) << endl; } int main () { func (add); func ([](int a,int b) -> int { return a * b; }); func (sub); }
1 2 3 func<int ()(int ,int )>((int (*)(int , int ))add); func<main::{lambda (int ,int )#1 }>(&fn); func<int (*)(int ,int )>(&sub);
类模板
不规定该类中某个函数的传参类型或返回值类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <string> #include <cassert> namespace mzt { template <class T > class vector { public : vector () : _a(nullptr ),_size(0 ),_capacity(0 ){} void push_back (const T& data) { if (_capacity == _size) { size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2 ; T* tmp = new T[newcapacity]; assert (tmp); _a = tmp; _capacity = newcapacity; } _a[_size++] = data; } T& operator [](size_t pos) { assert (pos < _size); return _a[pos]; } size_t getsize () { return _size; } private : T* _a; size_t _size; size_t _capacity; }; } using namespace std;int main () { mzt::vector<int > a; a.push_back (1 ); a.push_back (2 ); mzt::vector<double > b; b.push_back (3.0 ); b.push_back (4.0 ); }
类模板实例化与函数模板实例化不同
类模板实例化需要在类模板名字后跟 <>
指定类型(函数模板实例化也可以用 <>
指定类型,即使没有,编译器也会自动识别类型)
1 2 3 4 5 6 7 8 9 10 mzt::vector<int >::vector (&a); LODWORD (b._a) = 1 ;mzt::vector<int >::push_back (&a, (const int *)&b); LODWORD (b._a) = 2 ;mzt::vector<int >::push_back (&a, (const int *)&b); mzt::vector<double >::vector (&b); data = 3.0 ; mzt::vector<double >::push_back (&b, &data); data = 4.0 ; mzt::vector<double >::push_back (&b, &data);
模板特例化
在原模板类的基础上,针对特殊类型所进行特殊化的实现方式
模板特化中分为:函数模板特化,类模板特化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <cstring> using namespace std;bool IsEqual (int left, int right) { return left == right; } template <class T>bool IsEqual (const T& left, const T& right) { return left == right; } template < > bool IsEqual<const char *>(const char * const & left, const char * const & right) { return strcmp (left, right) == 0 ; } int main () { int a = 0 ; int b = 1 ; const char * p1 = "hello" ; const char * p2 = "hello" ; bool ret; ret = IsEqual (a, b); ret = IsEqual<int >(a, b); ret = IsEqual<const char *>(p1, p2); }
1 2 3 4 5 6 7 v4 = 0 ; v5 = 1 ; v6 = "hello" ; v7[0 ] = "hello" ; IsEqual (0 , 1 ); IsEqual<int >(&v4, &v5); IsEqual<char const *>(&v6, v7);
如果遇到相同普通函数(函数名和类型都相同),编译器则会优先调用普通函数(最优先)
如果指定类型匹配,特例化的模板函数会比普通的模板函数优先调用(次优先)
类型形参 & 非类型形参
模板参数分类:类型形参、非类型形参
类型形参:出现在模板参数列表中,跟在 class 或者 typename 之后的参数类型名称
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <cassert> namespace mzt { template <class T = int , size_t N = 10 > class Array { public : T& operator [](size_t pos) { return arr[pos]; } private : T arr[N]; }; } int main () { mzt::Array< > a; a[0 ]=1 ; a[1 ]=2 ; return 0 ; }
1 2 *mzt::Array<int ,10ul >::operator [](&a, 0LL ) = 1 ; *mzt::Array<int ,10ul >::operator [](&a, 1uLL ) = 2 ;
右值引用 左值 & 右值
左值是可以放在赋值号左边可以被赋值的值,左值必须要在内存中有实体
右值当在赋值号右边取出值赋给其他变量的值,右值可以在内存也可以在CPU寄存器
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> #include <memory> using namespace std;int main () { int a = 10 ; int &b = a; int &&c = 20 ; cout << &a << ":" << a << endl; cout << &b << ":" << b << endl; cout << &c << ":" << c << endl; }
1 2 3 0x7ffc2d216c30 :10 0x7ffc2d216c30 :10 0x7ffc2d216c34 :20
IDA 分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 v13 = 10 ; v15 = &v13; v14 = 20 ; v16 = &v14; v3 = std::ostream::operator <<(&std::cout, &v13); v4 = std::operator <<<std::char_traits<char >>(v3, ":" ); v5 = std::ostream::operator <<(v4, v13); std::ostream::operator <<(v5, &std::endl<char ,std::char_traits<char >>); v6 = std::ostream::operator <<(&std::cout, v15); v7 = std::operator <<<std::char_traits<char >>(v6, ":" ); v8 = std::ostream::operator <<(v7, *v15); std::ostream::operator <<(v8, &std::endl<char ,std::char_traits<char >>); v9 = std::ostream::operator <<(&std::cout, v16); v10 = std::operator <<<std::char_traits<char >>(v9, ":" ); v11 = std::ostream::operator <<(v10, *v16); std::ostream::operator <<(v11, &std::endl<char ,std::char_traits<char >>);
std::move
std::move
唯一的功能是将一个左值强制转化为右值引用
1 2 3 4 template <class _Ty >inline _CONST_FUN typename remove_reference<_Ty>::type&& move (_Ty&& _Arg) _NOEXCEPT { return (static_cast <typename remove_reference<_Ty>::type&&>(_Arg)); }
如果是左值,就通过 static_cast
将传进来的参数强转为右值并返回
如果是右值,直接返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> #include <utility> #include <vector> #include <string> using namespace std;int main () { std::string s = "Hello" ; std::string sleft; std::string sright; sleft = s; std::cout << s << endl; std::cout << sleft << endl; sright = std::move (s); std::cout << s << endl; std::cout << sright << endl; }
左值赋值:字符串 s 仍然存在
右值赋值:字符串 s 被置空
IDA 分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 std::allocator<char >::allocator (&v9, argv, envp); std::string::basic_string<std::allocator<char >>(v10, "Hello" , &v9); std::allocator<char >::~allocator (&v9); std::string::basic_string (v11); std::string::basic_string (v12); std::string::operator =(v11, v10); v3 = std::operator <<<char >(&std::cout, v10); std::ostream::operator <<(v3, &std::endl<char ,std::char_traits<char >>); v4 = std::operator <<<char >(&std::cout, v11); std::ostream::operator <<(v4, &std::endl<char ,std::char_traits<char >>); v5 = std::move<std::string &>(v10); std::string::operator =(v12, v5); v6 = std::operator <<<char >(&std::cout, v10); std::ostream::operator <<(v6, &std::endl<char ,std::char_traits<char >>); v7 = std::operator <<<char >(&std::cout, v12); std::ostream::operator <<(v7, &std::endl<char ,std::char_traits<char >>); std::string::~string (v12); std::string::~string (v11); std::string::~string (v10)
std::move
在底层会直接返回参数的原始值,真正置空字符串 s 的操作是 std::string::operator=()
函数
1 2 3 4 __int64 __fastcall std::move<std::string &>(__int64 a1) { return a1; }
仔细分析可以发现左值赋值和右值赋值调用的 std::string::operator=()
函数不一样
1 2 .text:0000000000002509 E8 E2 FC FF FF call __ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEaSERKS4_ ; std::string::operator =(std::string const &) .text:0000000000002509
1 2 .text:0000000000002577 E8 84 FD FF FF call __ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEaSEOS4_ ; std::string::operator =(std::string&&) .text:0000000000002577
拷贝构造函数 & 移动构造函数
如果想用其它对象初始化一个同类的新对象,只能借助类中的拷贝构造函数
其实现原理是为新对象复制一份和其它对象一模一样的数据
当类中拥有指针类型的成员变量时,拷贝构造函数中需要以深拷贝的方式复制该指针成员
移动构造函数使用右值引用形式的参数
在此构造函数中,num 指针变量采用的是浅拷贝的复制方式
在函数内部置空了 d.num(为了避免 “同一块对空间被释放多次”)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> using namespace std; class demo {public : demo ():num (new int (1 )){ cout<<"construct!" <<endl; } demo (const demo &d):num (new int (*d.num)){ cout<<"copy construct!" <<endl; } demo (demo &&d):num (d.num){ d.num = NULL ; cout<<"move construct!" <<endl; } ~demo (){ cout<<"class destruct!" <<endl; } int *num; }; demo get_demo () { return demo (); } int main () { demo a = get_demo (); demo b = a; demo c = move (a); return 0 ; }
IDA 分析:
1 2 3 4 5 6 7 get_demo ((demo *)v5);demo::demo ((demo *)v6, (const demo *)v5); v3 = std::move<demo &>(v5); demo::demo (v7, v3); demo::~demo ((demo *)v7); demo::~demo ((demo *)v6); demo::~demo ((demo *)v5);
1 2 3 4 5 6 7 8 9 10 11 void __fastcall demo::demo (demo *this , const demo *a2) { _DWORD *v2; __int64 v3; v2 = (_DWORD *)operator new (4uLL ); *v2 = **(_DWORD **)a2; *(_QWORD *)this = v2; v3 = std::operator <<<std::char_traits<char >>(&std::cout, "copy construct!" ); std::ostream::operator <<(v3, &std::endl<char ,std::char_traits<char >>); }
1 2 3 4 5 6 7 8 9 __int64 __fastcall demo::demo (_QWORD *a1, _QWORD *a2) { __int64 v2; *a1 = *a2; *a2 = 0LL ; v2 = std::operator <<<std::char_traits<char >>(&std::cout, "move construct!" ); return std::ostream::operator <<(v2, &std::endl<char ,std::char_traits<char >>); }
智能指针 智能指针 (Smart Pointer) 是一种在 C++ 中用于管理动态分配的内存的类,它提供了一种灵活的方式来管理对象的生命周期,可以避免资源泄漏和内存错误
auto_ptr
当对象过期时,其析构函数将自动使用 delete 来释放内存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <iostream> #include <memory> using namespace std;class Auto {public : Auto () { cout << "Auto create" << endl; } ~Auto () { cout << "Auto delete" << endl; } int key1 = 10 ; int key2 = 20 ; }; int main () { auto_ptr<Auto> test (new Auto) ; int key1 = (*test).key1; int key2 = test->key2; int key3 = key1 + key2; cout << key3 << endl; return 0 ; }
栈上的对象会在当前语句块结束时释放,堆上的对象需要使用 delete 释放
而 auto_ptr
属于栈上的对象,在它的析构函数中会尝试 delete 目标对象
IDA 分析如下:
1 2 3 4 5 6 7 8 v5 = (Auto *)operator new (8uLL ); Auto::Auto (v5); std::auto_ptr<Auto>::auto_ptr (v8, v5); v6 = *(_DWORD *)std::auto_ptr<Auto>::operator *(v8); v7 = v6 + *(_DWORD *)(std::auto_ptr<Auto>::operator ->(v8) + 4 ); v3 = std::ostream::operator <<(&std::cout, v7); std::ostream::operator <<(v3, &std::endl<char ,std::char_traits<char >>); std::auto_ptr<Auto>::~auto_ptr (v8);
在 auto_ptr
的析构函数中会自动释放 Auto(传入对象):
1 2 3 4 5 6 7 8 9 10 11 void __fastcall std::auto_ptr<Auto>::~auto_ptr (Auto **a1){ Auto *v1; v1 = *a1; if ( *a1 ) { Auto::~Auto (*a1); operator delete (v1, 8uLL ) ; } }
由 auto_ptr
创建的对象仍然具有指针的性质,其原因是 auto_ptr
对 *
,->
等符号进行了重构
1 2 3 4 __int64 __fastcall std::auto_ptr<Auto>::operator *(__int64 a1) { return *(_QWORD *)a1; }
1 2 3 4 __int64 __fastcall std::auto_ptr<Auto>::operator ->(__int64 a1) { return *(_QWORD *)a1; }
PS:对于 *
,->
的重构只是实现了解引用而已,不负责具体的偏移
unique_ptr
unique_ptr
和 auto_ptr
的用法几乎一样,另外添加了如下几个特性:
基于排他所有权模式:两个指针不能指向同一个资源
无法进行左值 unique_ptr
复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
在 STL 容器中使用 unique_ptr
,不允许直接赋值
支持对象数组的内存管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <iostream> #include <memory> using namespace std;int main () { unique_ptr<string> p1 (new string("11111111" )) ; unique_ptr<string> p2 (new string("22222222" )) ; cout << "p1:" << p1.get () << endl; cout << "p2:" << p2.get () << endl; unique_ptr<string> p3 (std::move(p2)) ; p2 = std::move (p1); cout << "-------------------" << endl; cout << "p1:" << p1.get () << endl; cout << "p2:" << p2.get () << endl; cout << "p3:" << p3.get () << endl; }
1 2 3 4 5 6 p1:0x559625bfdeb0 p2:0x559625bfdee0 ------------------- p1:0 p2:0x559625bfdeb0 p3:0x559625bfdee0
IDA 分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 std::allocator<char >::allocator (v27, argv, envp); v23 = (void *)operator new (0x20 uLL); std::string::basic_string<std::allocator<char >>(v23, "11111111" , v27); std::unique_ptr<std::string>::unique_ptr<std::default_delete<std::string>,void >(v25, v23); std::allocator<char >::~allocator (v27); std::allocator<char >::allocator (v27, v23, v3); v24 = (void *)operator new (0x20 uLL); std::string::basic_string<std::allocator<char >>(v24, "22222222" , v27); std::unique_ptr<std::string>::unique_ptr<std::default_delete<std::string>,void >(v26, v24); std::allocator<char >::~allocator (v27); ...... v10 = std::move<std::unique_ptr<std::string> &>(v26); std::unique_ptr<std::string>::unique_ptr (v27, v10); v11 = std::move<std::unique_ptr<std::string> &>(v25); std::unique_ptr<std::string>::operator =(v26, v11); ...... std::unique_ptr<std::string>::~unique_ptr (v27); std::unique_ptr<std::string>::~unique_ptr (v26); std::unique_ptr<std::string>::~unique_ptr (v25)
shared_ptr
shared_ptr
基于非排他所有权模式:允许多个指针指向同一个资源
当复制或拷贝时,引用计数加 “1”
当智能指针析构时,引用计数减 “1”
如果计数为 “0”,代表已经没有指针指向这块内存,那么就释放它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <iostream> #include <memory> using namespace std;void deleteStr (string* p) { cout << *p << endl; } int main () { string* str = new string ("11111111" ); std::shared_ptr<string> p1 (str, deleteStr) ; std::shared_ptr<string> p2 (p1) ; std::shared_ptr<string> p3 (p2) ; cout << "p1 count = " << p1.use_count () << endl; cout << "p2 count = " << p2.use_count () << endl; cout << "p3 count = " << p3.use_count () << endl; p1 = NULL ; p2 = NULL ; cout << "p1 count = " << p1.use_count () << endl; cout << "p2 count = " << p2.use_count () << endl; cout << "p3 count = " << p3.use_count () << endl; p3 = NULL ; cout << "22222222" << endl; return 0 ; }
1 2 3 4 5 6 7 8 p1 count = 3 p2 count = 3 p3 count = 3 p1 count = 0 p2 count = 0 p3 count = 1 11111111 22222222
weak_ptr
weak_ptr
是为配合 shared_ptr
而引入的一种智能指针,它只可以从一个 shared_ptr
或另一个 weak_ptr
对象构造
weak_ptr
提供的是一个弱引用:
shared_ptr
提供的是一个强引用:
当对象被创建时,计数为 “1”,每创建一个变量引用该对象时,该对象的计数就增加“1”,当上述变量销毁时,对象的计数减 “1”,当计数为 “0” 时,这个对象也就被析构了
强引用计数在很多种情况下都是可以正常工作的,但是也有不凑效的时候,当出现循环引用时,就会出现严重的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <memory> using namespace std;class parent ;class children ;typedef shared_ptr<parent> parent_ptr;typedef shared_ptr<children> children_ptr;typedef weak_ptr<parent> parent_ptr2;class parent {public : ~parent () { std::cout <<"destroying parent\n" ; } public : children_ptr children; }; class children {public : ~children () { std::cout <<"destroying children\n" ; } public : parent_ptr parent; }; void test () { parent_ptr father (new parent()) ; children_ptr son (new children()) ; father->children = son; son->parent = father; cout << "father count = " << father.use_count () << endl; cout << "son count = " << son.use_count () << endl; } int main () { std::cout<<"begin test\n" ; test (); std::cout<<"end test\n" ; }
由于 parent
和 children
对象互相引用,它们的引用计数都是 “2”,不能自动释放
并且此时这两个对象再无法访问到
1 2 3 4 begin test father count = 2 son count = 2 end test
使用弱引用 weak_ptr
即可打破循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 #include <iostream> #include <memory> using namespace std;class parent ;class children ;typedef shared_ptr<parent> parent_ptr;typedef shared_ptr<children> children_ptr;typedef weak_ptr<parent> parent_ptr2;class parent {public : ~parent () { std::cout <<"destroying parent\n" ; } public : children_ptr children; }; class children {public : ~children () { std::cout <<"destroying children\n" ; } public : parent_ptr2 parent; }; void test () { parent_ptr father (new parent()) ; children_ptr son (new children()) ; father->children = son; son->parent = father; cout << "father count = " << father.use_count () << endl; cout << "son count = " << son.use_count () << endl; } int main () { std::cout<<"begin test\n" ; test (); std::cout<<"end test\n" ; }
1 2 3 4 5 6 begin test father count = 1 son count = 2 destroying parent destroying children end test