Successfully reported this slideshow.

C++编程实践

3,365 views

Published on

  • Be the first to comment

C++编程实践

  1. 1. 员工培训课程 C++ 编程最佳实践 2 0 0 5 .9 .1 9 版本 V1.00
  2. 2. C++ 编程最佳实践 学 时 数: 5 学时 ( 50 分钟 / 学时) 课程目的: C++ 作为最灵活的编程语言之一,有许多的 陷阱,也有许多的宝贵经验与高级技巧,本课程将对如何 避免 C++ 中的陷阱、掌握一些好的 C++ 实践,对有一定 开发基础的新员工提高软件开发的效率和软件质量是很有 作用的。 课程内容:本课程结合一定的例子分别介绍 C++ 基础、 对象模型、内存管理和一些 tip 等 3 个部分内容。 适合对象:具有一定开发基础的新员工 2
  3. 3. C++ 基础 更好的 C 对象基础 继承和组合 3
  4. 4. 更好的 C 1. 常量 #define BUFF_SIZE 1024 const BUFF_SIZE=1024; const int BUFF_SIZE=1024; #define 采用的是一种预处理的方式,只是一种文本代替,不进行类型检查 编译器方式实现 const 常量可以支持更复杂的使用方法,如类常量 const 定义一般在头文件里使用, const 是内部连接,可以避免连接错误 规定在变量寿命期内内容不会变化(你不能通过 define 来实现这种用法) 4
  5. 5. 更好的 C 1. 指针常量 如何读指针定义:从标识符开始,由里向外读 const int* x; int const * x; x 是一个指针,它指向一个 const int x 是一个指向恰好是 const 的 int 的普通指针 int * const x=&d; x 是一个指针,这个指针是指向 int 的 const 指针 可以利用 *x=2 改变指向单元的值,但你不能 x=&c 改变 x 的值 const int* const x=&d; int const * const x2=&d; 指针、指针指向的对象都不能变 意义:赋值时的类型检查 5
  6. 6. 更好的 C 1. 函数参数和返回值中的常量 void f1(const int i) { i++; //illegal } 变量 i 的初值不会被函数 f1 改变(在 f1 的活动期该值不变) const int f2() const 的返回值,不会对内部数据产生影响 对于用户定义数据类型,如类,该函数的返回值不能是左值(不能被赋值,也 不能被修改) 临时变量 6
  7. 7. 更好的 C 1. 变量块内定义 for (int ii=0;ii<100;ii++ ) { int dwPos; …… { int dwPos; …… } …… } 变量块内变量的作用域和生存期 7
  8. 8. 更好的 C 1. 引用 int x; Int &a=x; a++; const int &pi=1024; a=pi; 引用,又称别名,是一个自动能被编译器逆向引用的常量型指针 使用引用的规则 •引用被创建时,必须被初始化(指针则可以在任何时候被初始化); •引用被初始化为指向一个对象,它就不能被改变为对另一个对象的引用(指针则可以在任何时候指 向另一个对象); •不存在 NULL 引用。必须确保引用是和一块合法的存储单元关联。 const 引用可以用不同类型的对象初始化,也可以是不可寻址的值,如文字常量。 引用和指针的区别 •引用必须总是指向一个对象 •引用间的赋值,改变了被引用的对象而不是引用本身 8
  9. 9. 更好的 C 1. 函数中的引用 int& g (int& x) { x++; return x; } 引用被作为函数参数时,传递的是实参的左值。函数内任何对引用的更改将对函数外的参数 改变 使用引用作为函数参数的 3 种情况 •函数中需要改变实参的值; •向调用者返回额外的结果; •大型对象。 9
  10. 10. 更好的 C 1. 名字空间和域操作符 namespace eb { void showName (); const char* name=“EAST Information Technology Co. Ltd.”; } printf(“%sn”,eb::name); using namespace eb; 全局名字空间和全局名字空间污染 名字空间和全局名字的限定修饰 名字空间别名、 using 声明、 using 指示符 域操作符 :: 标准名字空间 std 10
  11. 11. 更好的 C 1. new 、 delete 操作符 #include <stdlib.h> char *p=(char*) malloc (sizeof(char) * 100); free(p); char *p=new char; delete p; char *prg=new char [1024]; delete [] prg; 动态分配对象,用于程序员控制对象的生存期 C 中使用函数进行内存管理, C++ 使用运算符进行 11
  12. 12. 更好的 C 1. 内联函数 #define floor(x,b) x>=b?0:1 if(floor(a&0x0f,0x07)) … … #define band(x) (((x)>5 && (x)<10) ? (x) : 0) band(++a) inline int band (int x) { if (x>5 && x<10) return x; return 0; } C 中使用预处理器,来保证效率,但带来了问题 C++ 的内联函数,在任何意义上都是真正的函数,和函数唯一不同的是内联函数在适当的 时候象宏一样被展开,说有函数调用的开销被取消 内联函数应定义在头文件中,并且默认为 static 内联函数失效 •内联函数过于复杂,如出现递归(直接或间接递归) •有指向内联函数的函数指针 12
  13. 13. 更好的 C 1. 缺省实参 int setDefault (int dwDefault=10) ; char screenInit (int height, int width, char background = ‘ ‘ ); screenInit (24, 80); screenInit (24, 80, ‘*’ ); 函数可以为一个或多个参数指定缺省实参 缺省实参只能用来替换调用缺少的尾部实参 13
  14. 14. 更好的 C 1. 函数重载 int max (int, int) int max (const vector<int> & ); int max (const matrix & ); 函数重载( function overloading )允许多个函数共享同一个函数名,但是针对不同参数类 型提供共同的操作。 int max (int, int) int max (int a, int b ); 函数重复声明 int max (int, int) double max (int a, int b ); 错误函数重复声明 如果函数的功能不一样,请不要利用重载 14
  15. 15. 更好的 C 1. 函数重载解析 函数重载解析( function overload resolution )是把函数调用与重载函数集合中的一个函数 相关联的过程。 void f(); void f( int ); void f( double , double = 3.4 ); void f(char*, char*); f(5.6); 函数重载解析的过程如下: 1. 确定函数调用考虑的重载函数集合,确定函数调用中实参列表 2. 从重载函数集合中选择函数,该函数可以在给出实参个数和类型的情况下用调用中指定的 实参进行调用 3. 选择与调用最匹配的函数 函数重载是静态联编 15
  16. 16. 更好的 C 1. 异常 enum Estate { noErr, zeroOp, noSpace } 异常是程序可能检测到的、运行时刻不正常的情 int fun (Type* aType) { 况。 int dwPos=0; 异常通过 throw 抛出。 while ( … ) … ; try 块必须包围能够产生异常的语句,以关键字 if (dwPos==max_pos) throw noSpace; try 开始,后面是花括号括起语句序列。 … 异常通过 catch 子句来捕获。 catch 子句包括关 } 键字 catch ,括号中的单个类型或对象声明,后 void g () { 面是花括号括起语句序列。 try { 异常可以被重新抛出。 Type* pType=new Type; Type* pType=new Type; fun (pType); fun (pType); } catch (Estate ) { } catch ( … ) { printf(“NO Estate Exceptionn”); throw; } } 16
  17. 17. 对象基础 1. 声明和定义 类和对象 存储控制 构造函数和析构函数 对象的静态成员 运算符重载 引用和拷贝构造函数 对象的动态创建 this 指针 17
  18. 18. 对象基础 1. 声明和定义 extern int i; 声明 定义 extern float f (float); 定义 float b; float f (float a) { return a+1.0; } 定义并声明 int h (int x) { return x+1; } 声明用于向计算机介绍名字,它说“这个名字是什么意思”。 定义将为名字分配空间 18
  19. 19. 对象基础 1. 类和对象 这个世界是由什么组成的? 化学家:“这个世界是由分子、原子、离子等等的化学物质组成的”。 画家呢:“这个世界是由不同的颜色所组成的”。 分类学家:“这个世界是由不同类型的物与事所构成的” 类描述了一组有相同特性(属性)和相同行为(方法)的对象。 对象是类的实例。 19
  20. 20. 对象基础 1. 类定义 class Screen { short _height; short _width; short _x, _y; public: void home (); bool checkRange (int, int, ); } 类头:由关键字 class 和后面的类名构成。 类体:类体由一对花括号包围起来,定义了类成员表。 类数据成员 类成员函数 20
  21. 21. 对象基础 1. 存储控制 class Screen { public: short _height, _width; bool checkRange (int, int, ); protected: int _x, _y; bool _checkRange (int, int ); private: int _ftype; bool gerType(); } 公有成员:在程序的任何地方都可以访问。 私有成员:被成员函数和类的友元访问 被保护成员:对派生类就像公有成员一样,对其它程序表现的像私有成员 21
  22. 22. 对象基础 1. 友元 class Screen { friend void printfScreen ( Screen& ); private: int _ftype; int gerType(); }; void printfScreen ( Screen& aScreen) { printf(“THE SCREEN TYPE IS:%dn”, aScreen.ftype); printf(“THE SCREEN TYPE IS:%dn”, aScreen. gerType()); } 在某些情况下允许某个函数,而不是整个程序可以访问类的私有成员,这样做会比较方便。 友元机制允许一个类授权其它函数访问它的非公有成员。 22
  23. 23. 对象基础 1. const 作用于成员函数 class Screen { 该成员函数不会修 public: 改成员变量 bool isEqual (char ch ) const; inline void move ( int r, int c ) const; private: int _ftype; inline void Screen::move ( int r, int c ) const volatile boolean _fOn; { int gerType(); _cursor = row + c -1; mutable int _cursor; }; }; 在某些情况下允许某个函数,而不是整个程序可以访问类的私有成员,这样做会比较方便。 友元机制允许一个类授权其它函数访问它的非公有成员。 volatile 用于说明该变量可能被编译器无法侦测的方式被修改,如何 I/O 端口相关的数据结构 mutable 修饰的变量可以被 const 成员函数修改 23
  24. 24. 对象基础 1. this 指针 每一个对象都有一个 this 指针。该指针用于成员函数访问对象的成员变量 当一个成员函数需要返回该成员函数被应用得对象时,需要用到 this* 。 Screen& Screen::display() { … … // 可能的处理 return *this; } 24
  25. 25. 对象基础 1. 构造函数和析构函数 25
  26. 26. 对象基础 1. 对象的静态成员 26
  27. 27. 对象基础 1. 运算符重载 27
  28. 28. 对象基础 1. 引用和拷贝构造函数 28
  29. 29. 对象基础 1. 对象的动态创建 29
  30. 30. C++ 进阶 对象模型 构造函数的语义 数据模型 函数模型 构造、析构和拷贝模型 执行时 30
  31. 31. 对象模型 1. C++ 在布局以及存取时间上的主要额外负担是由 virtual 引 起的 C++ 的对象数据类型有 static , nonstatic C++ 的对象方法有 static , nonstatic 和 virtual 31
  32. 32. 对象模型 1. 对象模型(不带多重继承) class Point { public: Point (float xval); virtual ~Point(); float x() const; float _x Type_info for Point _vptr__Point static int PointCount(); Point::~Point() protected: Point::print(ostream&) Virtual table virtual ostream& print (ostream&os) const; for Point Point::Point(float) float _x; static int static int static int _point_count; Point:: Point::PointCount() _point_count float Point::x() } 32
  33. 33. 对象模型 1. 对象模型(多重继承) class iostream: public istream, public ostream {… …}; class istream: virtual public ios {… …}; class ostream: virtual public ios {… …}; istream class object iostream class object bptr ios class base class bptr object table for ostream class istream object base class table for bptr iostream base class table for ostream 33
  34. 34. 对象模型 1. struct 的问题 struct mumble { char pc[1]; }; struct mumble *pmumb1 = ( struct mumble* ) malloc ( sizeof (struct mumble ) + strlen (string) +1 ) strcpy ( &mumble.pc, string ); struct 的 class 意义 (建议:不要利用 struct 代替 class ) 以上例子中,将 struct 换成 class ,有时候可以正确运行,有时候会失败 如果需要在 C++ 中实现以上功能,请使用组合机制 class Mumble { private mumble _mumble; }; 34
  35. 35. 对象模型 1. 对象的数据组织 class ZooAnimal { public: int loc ZooAnimal(); virtual ~ ZooAnimal(); int String::len protected: char* String::str int loc; __vptr_ZooAnimal String name; }; ZooAnimal za(“Zoey”) ZooAnimal za(“Zoey”); ZooAnimal *pza=&za; 1000 ZooAnimal *pza=&za; 35
  36. 36. 对象模型 1. 对象的数据组织 int loc int String::len class Bear : public ZooAnimal { char* String::str public: Bear(); __vptr_ZooAnimal virtual ~Bear(); protected: int cell_block int cell_block; }; Bear b(“Yogi”) Bear b(“Yogi”); 1000 ZooAnimal *pza=&b; Bear *pzb=&b; ZooAnimal *pza 1000 Bear *pzb 36
  37. 37. 对象模型 1. 对象大小 对象的 nonstatic 数据成员的总和大小 Aligment 填充的空间 virtual 产生的负担 37
  38. 38. 构造函数的语义 1. 缺省构造函数 对于 class X ,如果没有任何用户定义的构造函数,那么就会有一个缺省构造函数被声明出 来,这个缺省构造函数是一个 trivial 的构造函数 在以下情况下,编译器将为用户产生 nontrivial 的构造函数 •带有 Default Constructor 的 Member Class Object •带有 Default Constructor 的 Base Class •带有一个 Virtual Function 的 Class •带有一个 Virtual Base Function 的 Class 38
  39. 39. 构造函数的语义 1. 类带有缺省构造函数的类对象成员 类没有任何构造函数,但类的成员对象有缺省构造函数,那编译器必须 为该类产生一个非 trivial 的构造函数 class Foo { 编译器为 Bar 产生的缺省构造函数将是: public: inline Bar::Bar() { Foo(), foo.Foo::Foo(); Foo (int) } }; class Bar { 如果用户定义了 Bar 的构造函数 public: Bar::Bar() { Foo foo; str = 0; char* str; } }; 编译器的动作应该是 void foo_bar() { Bar bar; Bar::Bar() { if(str) {… } … foo.Foo::Foo(); } str = 0; } 39
  40. 40. 构造函数的语义 1. 类继承自带有缺省构造函数的类 类没有任何构造函数,但类继承的父类有缺省构造函数,那编译器必须 为该类产生一个非 trivial 的构造函数 class Foo { 编译器为 Bar 产生的缺省构造函数将是: public: inline Bar::Bar() { Foo(), Foo::Foo(); Foo (int) } }; class Bar : Foo { public: char* str; }; void foo_bar() { Bar bar; if(str) {… } … } 40
  41. 41. 构造函数的语义 1. 类带有虚函数 类声明(或继承)一个虚函数或类的某一个父类带有一个或多个虚基类 一个虚函数表会被编译器产生出来,内放类的虚函数的地 class Widget { 址 public: 一个额外的指针成员,指向相应的虚函数表 virtual void flip () = 0; // … 编译器为 Bar 产生的缺省构造函数将是: }; inline Bell::Bell() { this->vptr=… …; //Bell 和 Whistle 继承自 Widget } void foo () { { Bell b; Whistle w; b.flip(); w.flip(); } 41
  42. 42. 构造函数的语义 1. 类带有一个虚基类 class X {public: int I;}; 不同的编译器有不同的实现方法 class A : public virtual X 编译器将改写 foo 并为 A , B , C 产生缺省构造函数 {public: int j; }; void foo (const A* pa) { pa->vbcX->I = 1024; } // 编译器需要在 A , B , C 构造函数中对 vbcX 进行赋值 class B : public virtual X , {public: double d; }; // 需要生成缺省构造函数 class C: public A, public B {public: int k; }; void foo (const A* pa) { pa->i=1-24; }; foo (new A); foo (new B); 42
  43. 43. 构造函数的语义 1. 关于缺省构造函数的两个误解 如果类没有定义缺省构造函数,编译器将会合成出来一个 编译器合成的缺省构造函数会为类的每一个成员变量设定默认值 #include <stdio.h> int main () { class A test(12); { test(32); public: test(112); int i return 0; }; } void test (int newVal) { A a; printf(“a.i=%dn”; a.i); a.i=newVal; } 43
  44. 44. 构造函数的语义 1. 拷贝构造函数的构建工作—何时使用拷贝构造函数 对一个 object 进行初始化工作 当 object 被作为参数传给某一个函数 函数传回一个 object class X {… …}; X foo_bar() X x; { X xx=x; X xx; …… return xx extern void foo (X x); } void bar() { X xx; … foo(xx); } 44
  45. 45. 构造函数的语义 1. 缺省按成员初始化 对于内建或派生的数据成员(如指针和数组),从某个 object 拷贝一份到另一个 object 上 对于对象的类成员,利用该成员的拷贝构造函数,如果没有,递归产生按成员初始化的拷贝 构造函数 按位拷贝(不需要编译器合成缺省拷贝构造函数) class String { String( String& aString) public: { // … … str=aString.str; private: len=aString.len; char *str; } int len; } verb.str=noun.str; verb.len=noun.len String noun ( “book” ); String verb = noun; 45
  46. 46. 构造函数的语义 1. 按位拷贝失效 类的某一个成员是一个对象,该对象的类有拷贝构造函数(不论是声明的,还是编译器合成 的) 类继承自一个有拷贝构造函数(不论是声明的,还是编译器合成的)的基类 类有虚函数 类的某一个祖先是虚基类 46
  47. 47. 构造函数的语义 1. 重新设定 VTable 指针 ( 按位拷贝失效 ) 如果类有虚函数,编译器将: 增加一个 vtable ,内含每一个有作用的虚函数的地址 将一个指向 vtable 的指针 vptr 安插在每一个对象内 拷贝构造函数的一个重要工作,就是设置好对象的 vptr (如果有的话) class ZooAnimal class Bear : public ZooAnimal Virtual table { { for Bear vptr public: public: Bear::draw() ZooAnimal() {type='Z';} ; Bear() {type='B';}; virtual ~ZooAnimal() {}; void draw(); Virtual table }; for ZooAnimal vptr virtual void draw(); ZooAnimal::draw() char type; Bear aBear; bZooAnimal=aBear }; ZooAnimal bZooAnimal=aBear; ZooAnimal& rZooAnimal=aBear; 47
  48. 48. 构造函数的语义 1. 虚继承情况下的拷贝构造函数 如果类是虚继承,那么按位拷贝构造也将失败 在这种情况下,拷贝构造函数的一个重要工作,就是设置好虚基类的位置,主要应用于该对 象被赋值,初值是它的子类 如果是同类赋值,只需要按位拷贝就可以了 48
  49. 49. 构造函数的语义 1. 参数初始化时的拷贝构造函数 void foo (X x0); void foo (X& x0); X xx; X __temp0; foo (xx ); __temp0.X::X (xx); foo (__temp0) ; __temp0.X::~X (); 49
  50. 50. 构造函数的语义 1. 返回值初始化时的拷贝构造函数 X bar () void bar (X& __result) { { X xx; X xx; // … … xx.X::X(); //X 的构造函数 return XX; // … … } __result.X::X (xx); xx.X::~X(); X ax=bar(); return; } X __temp; bar(__temp); ax.X::X( __temp ); __temp.X::~X(); 50
  51. 51. 构造函数的语义 1. 成员的初始化表 需要利用初始化表的情况 •类中有引用成员需要初始化 •类中有常量成员需要初始化 •需要利用基类的初始化表 •需要利用成员对象的初始化表 Word::Word (Word* __pthis) { 效率问题 _name.String::String(); class Word { String temp=String ( 0 ); String _name; _name.String::operator = ( temp ); int _cnt; temp.String::~String(); public: Word() { _cnt = 0; _name=0; } _cnt=0; } Word::Word (Word* __pthis) } { _name.String::String ( 0 ); _cnt = 0; Word:: Word : _name ( 0 ) { _cnt = 0; } } 51
  52. 52. 构造函数的语义 1. 成员的初始化表顺序 按成员的声明顺序进行 class X { class X { int i; int i; int j; int j; public: public: X( int val ) : j( val), i(j) X( int val ) : j( val) {} { i=j; } } } 52
  53. 53. Data 语义学 1. 如何解释下面的结果 #include <stdio.h> class X {}; class Y : public virtual X {} ; class Z : public virtual X {} ; class A : public Y, public Z {} ; size X of is 1 size Y of is 4 int main () { size Z of is 4 printf(quot;size X of is %dnquot;,sizeof(X)); size A of is 8 printf(quot;size Y of is %dnquot;,sizeof(Y)); printf(quot;size Z of is %dnquot;,sizeof(Z)); size X of is 1 printf(quot;size A of is %dnquot;,sizeof(A)); size Y of is 8 } size Z of is 8 size A of is 12 53
  54. 54. Data 语义学 1. 对象的大小 对象的 nonstatic 数据成员的总和大小 Aligment 填充的空间 virtual 产生的负担 size X of is 1 size Y of is 8 size Z of is 8 size A of is 12 X 是 1 个字节,是为了每个对象有不同的地址( this 会指向不同的地方) Y 、 Z 是 8 个字节,首先是 vptr ,然后是 1 个字节,和这个字节的填充 A 是 12 个字节,首先是 2 个 vptr ,然后是 1 个字节,和这个字节的填充 size X of is 1 size Y of is 4 size Z of is 4 size A of is 8 X 是 1 个字节,是为了每个对象有不同的地址( this 会指向不同的地方) Y 、 Z 是 4 个字节,是 vptr ,这个 vptr 能够区分不同的对象,就不再引入一个字节 A 是 12 个字节,首先是 2 个 vptr ,这两个 vptr 能够区分不同的对象,就不再引入一个字节 54
  55. 55. Data 语义学 1. 数据成员的布局 静态成员不会影响数据布局 C++ 规范规定,同一个 access 段中,较晚出现的成员在对象中有较高的地址(边界调整) C++ 规范允许不同 access 段的数据成员可以自由排列 class Point3d { private: float x; class Point3d { static List <point3d*> *freeList; private: float y; float x; static const int chunkSize=250; static List <point3d*> *freeList; float z; private: }; float y; static const int chunkSize=250; private: float z; }; 55
  56. 56. Data 语义学 1. 访问静态数据成员 静态成员,被编译器视为全局变量(只是在对象的生命范围内可见),其开销相当于全局变 量 class Point3d { private: float x; static List <point3d*> *freeList; float y; static const int chunkSize=250; float z; }; Point3d foo(); Point3d origin, *pt=&origin; Point3d::chunkSize == 250 origin.chunkSize==250; Point3d::chunkSize == 250 pt->chunkSize ==250; foo(); Point3d::chunkSize == 250 foo(). chunkSize ==250 56
  57. 57. Data 语义学 1. 访问非静态数据成员 成员函数通过 this 指针访问数据成员 class Point3d { private: float x; static List <point3d*> *freeList; float y; static const int chunkSize=250; float z; }; Point3d Point3d::translate (Point3d* const this, Point3d const Point3d &pt) { Point3d::translate ( const Point3d &pt) { this->x+=pt.x; x+=pt.x; this->y+=pt.y; y+=pt.y; this->z+=pt.z; z+=pt.z; } } 57
  58. 58. Data 语义学 1. 访问非静态数据成员 当 Point3d 是一个子类,其继承结构中有一个虚基类,同时 x 是虚基类的成员时, origin.x 和 pt->x 的访问有重大区别差别 class Point3d { public: float x; static List <point3d*> *freeList; float y; static const int chunkSize=250; float z; }; Point3d origin, *pt=&origin; origin.x=0.0; pt->x=0.0; 58
  59. 59. Data 语义学 1. 继承情况下访问数据成员 不带继承的情况 class Point2d { float x; public: float x; float y; float y; }; class Point3d { float x; public: float x; float y; float y; float z; float z; }; 59
  60. 60. Data 语义学 1. 继承情况下访问数据成员 继承但没有多态 继承但没有多态,可能导致对象膨胀 class Point2d { public: float x; float y; float x; float x float x float y; }; char x char x class Point3d : public Point2d { float x; public: float y; char x float z; float z; }; 60
  61. 61. Data 语义学 1. 继承情况下访问数据成员 单一继承并包含虚函数 float x; __vptr_Point2d class Point2d { float y; float x; public: __vptr_Point2d float y; virtual float getz { return 0.0; }; float x; float y; }; float x; __vptr_Point2d float y; float x; class Point3d : public Point2d { __vptr_Point2d float y; public: float z; float z; float z; float getz { return z; }; }; 61
  62. 62. Data 语义学 1. 继承情况下访问数据成员 多重继承但不包含虚继承 Vertex3d v3d; Point2d Vertex Vertex *pv; Point2d *p2d; Point3d Point3d *p3d; pv= (Vertex*) (((char*)&v3d) Vertex3d +sizeof(Point3d)); pv=&v3d; p2d=&v3d; p2d=&v3d; p3d=&v3d; p3d=&v3d; float x; float x; float y; float y; __vptr_Point2d __vptr_Point2d Vertex *next; float z; float x; __vptr_Vertex Vertex *next; float y; __vptr_Vertex __vptr_Point2d float mumble; float z; 62
  63. 63. Data 语义学 1. 继承情况下访问数据成员 含虚继承的情况 void Point3d:: operator+= (const Point3d &rhs) { Point2d x+=rhs.x; pPoint2d->x+=rhs.pPoint2d->x; y+=rhs.y; Vertex Point3d pPoint2d->y+=rhs.pPoint2d->y; z+=rhs.z; z+=rhs.z; } Vertex3d float x; float y; Vertex *next; Vertex *next; __vptr_Point2d Point2d *pPoint2d Point2d *pPoint2d __vptr_Vertex __vptr_Vertex float x; float z; float z; float y; Point2d *pPoint2d Point2d *pPoint2d __vptr_Point2d __vptr_Point3d __vptr_Point3d float mumble; float x; float x; float y; float y; __vptr_Point2d __vptr_Point2d 63
  64. 64. Data 语义学 1. 继承情况下访问数据成员 含虚继承的情况 Point2d Point2d* p2d=pv3d ? Vertex Point3d Point2d* p2d=pv3d; pv3d->pPoint2d : 0; Vertex3d float x; float y; Vertex *next; Vertex *next; __vptr_Point2d Point2d *pPoint2d Point2d *pPoint2d __vptr_Vertex __vptr_Vertex float x; float z; float z; float y; Point2d *pPoint2d Point2d *pPoint2d __vptr_Point2d __vptr_Point3d __vptr_Point3d float mumble; float x; float x; float y; float y; __vptr_Point2d __vptr_Point2d 64
  65. 65. Data 语义学 1. 继承情况下访问数据成员 含虚继承的情况 float z; 8 __vptr_Point3d 表偏移量(8) Point2d float x; 虚函数表 float y; Vertex Point3d __vptr_Point2d …… Vertex3d Vertex *next; 8 __vptr_Vertex (8) void Point3d:: float x; …… operator+= (const Point3d &rhs) float y; { __vptr_Point2d …… x+=rhs.x; y+=rhs.y; Vertex *next; z+=rhs.z; __vptr_Vertex (20) 20 float z; …… } 12 __vptr_Point3d float mumble; (12) (this + __vptr__Point3d[-1]) -> x+ = float x; …… float y; (&rhs + __vptr__Point3d[-1]) -> x; __vptr_Point2d …… (this + __vptr__Point3d[-1]) -> y+ = (&rhs + __vptr__Point3d[-1]) -> y; z+=rhs.z; 65
  66. 66. Data 语义学 1. 继承情况下访问数据成员 float z; 8 __vptr_Point3d 表偏移量(8) 含虚继承的情况 float x; 虚函数表 float y; Point2d __vptr_Point2d …… Vertex Point3d Vertex *next; 8 __vptr_Vertex (8) Vertex3d float x; …… float y; __vptr_Point2d …… Point2d* p2d=pv3d; Vertex *next; Point2d* p2d=pv3d ? __vptr_Vertex (20) pv3d->__vptr__Point3d[-1] : 0; 20 float z; …… 12 __vptr_Point3d float mumble; (12) float x; …… float y; __vptr_Point2d …… 66
  67. 67. Data 语义学 1. 指向数据成员的指针 操作 &Point3d::z 将得到 z 在对象中的偏移量 操作 &p3d.z 将得到 z 在内存中的位置 class Point3d { public: virtual ~Point3d(); protected: static Point3d origin; float x, y, z; }; Point3d p3d; 67
  68. 68. Function 语义学 1. 非静态非虚拟成员函数的编译过程 改写函数原形并添加一个新的成员,用它间接访问成员变量(非静态) 对每一个非静态成员的访问,改为经由 this 指针来访问 将函数名进行 mangling 处理,使它在程序中变为一个独一无二的名字 对函数调用进行处理 编译器有一个算法,用于为不同的符号产生唯一的命名 float Point3d::magnitude () { extern float float return sqrt ( x*x + y*y + z*z ); magnitude__7Point3dFv (Point3d* const Point3d:: magnitude (Point3d* const this) this) { } return sqrt( this->x*this->x + obj. magnitude (); this->y*this->y + ptr-> magnitude (); this->z*this->z ); } magnitude__7Point3dFv (&obj); magnitude__7Point3dFv (ptr); 68
  69. 69. Function 语义学 1. 虚拟成员函数的编译过程 vptr 是编译器产生的指针,指向 vtable 1 是 vtable 的索引,关联到 magnitude 函数 第二个 ptr 是 this 指针 //magnitude 是虚函数 (* ptr -> vptr [1] ) (ptr); ptr-> magnitude (); 69
  70. 70. Function 语义学 1. Type_info for Point float x; Point::~Point() Pure_virtual_call() __vptr_Point vtable Point::y() Point::z() ptr-> z (); Type_info for Point2d float x; ( *ptr -> vptr [4] ) (ptr); Point2d::~Point2d() Point2d::mult() __vptr_Point Point2d::y() Point::z() float y; Type_info for Point3d float x; Point3d::~Point3d() Point3d::mult() __vptr_Point Point2d::y() Point3d::z() float y; float z; 70
  71. 71. Function 语义学 1. Type_info for Base1 Data_Base1 Base1::~Base1() 多重继承 vtable 调整 Base1::SpeakClearly() __vptr_Base1 Base1::clone() 多重继承时处理虚函数的主要困难在于 Base2 的 vtable 上。在图中,主要体现在 Derived 的析构函数和 Base2::mumble() Type_info for Base2 Data_Base2 和 clone() 。 Base2::~Base2() 对指针进行调整,使指针的任何非多态运 Base2::mumble() __vptr_Base2 用都不会失败,如访问 Base2 的成员变量 Base2::clone() Base2* pbase2 = new Derived; Derived* temp = new Derived; Type_info for Derived Data_Base1 Base2 * pbase2 = temp? Derived::~Derived() Base1::SpeakClearly() temp + sizeof (Base1) : 0 __vptr_Base1 Derived::clone() Base2::mumble() ** Data_Base2 Type_info for Derived __vptr_Base2 Derived::~Derived() ** Base2::mumble() float z; Derived::clone() ** 71
  72. 72. Function 语义学 1. Type_info for Base1 Data_Base1 多重继承 vtable 调整 Base1::~Base1() Base1::SpeakClearly() 析构函数需要对指针进行调整,指回原来 __vptr_Base1 Base1::clone() 的分配的起始地方 对于析构来说,并不一定每次都需要调整 指针,如 pbase2=new Base2 的情形 Type_info for Base2 Data_Base2 更重要的是,调整的大小需要到执行的时 Base2::~Base2() 候才能确定 Base2::mumble() __vptr_Base2 Base2* pbase2 = new Derived; Base2::clone() delete pbase2; Derived* temp = new Derived; Type_info for Derived Data_Base1 Base2 * pbase2 = temp? Derived::~Derived() temp + sizeof (Base1) : 0 Base1::SpeakClearly() __vptr_Base1 Derived::clone() Base2 * pbase2 = pbase2 ? Base2::mumble() ** Data_Base2 pbase2 - sizeof (Base1) : 0; (*pbase2->__vptr[1]) (pbase2 ); Type_info for Derived __vptr_Base2 // (*pbase2->__vptr[1]) Derived::~Derived() ** // (pbase2 - sizeof (Base1) ); Base2::mumble() float z; free(pbase2); Derived::clone() ** 72
  73. 73. Function 语义学 1. Type_info for Base1 Data_Base1 多重继承 vtable 调整 Base1::~Base1() Base1::SpeakClearly() 在 vtable 中引入 offet __vptr_Base1 Base1::clone() 不同的析构函数 对于 __vptr_Base1 的 mumble 函数(继 承过来,但没有改写),有类似的处理 Type_info for Base2 Data_Base2 delete pbase2; Base2::~Base2() Base2::mumble() __vptr_Base2 Base1 *pbase1 = new Derived; Base2::clone() delete pbase1; (*pbase2->vptr[1]) (pbase2); // 没有多重继 Type_info for Derived Data_Base1 承 Derived::~Derived() Base1::SpeakClearly() (*pbase2->vptr[1].faddr) __vptr_Base1 Derived::clone() (pbase2+ pbase2->vptr[1].offset); Base2::mumble() ** Data_Base2 //pbase2 指向的析构函数 Type_info for Derived { this-=sizeof(Base1); __vptr_Base2 Derived::~Derived() ** Derived::~ Derived(this); } Base2::mumble() //pbase1 指向的析构函数 float z; Derived::clone() ** { Derived::~ Derived(this); } 73
  74. 74. Function 语义学 1. 虚拟继承下的多态 静态成员函数的编译过程 静态成员函数不需要 this 指针 静态成员函数只能访问类的静态成员变量 它不能被声明为 const 、 volatile 或 virtual 不需要经过 class object 就可以调用 obj. magnitude (); magnitude__7Point3dSFv(): ptr-> magnitude (); magnitude__7Point3dSFv(): 74
  75. 75. Function 语义学 1. 指向成员函数的指针 定义和使用指向成员函数的指针 double // 返回类型 ( Point::* // 类 pmf ) // 名称 (); // 参数列表 double (Point::*coord) () = &Point::x; coord=&Point::y; (origin.*coord) (); (ptr->*coord) (); (coord) (&origin); (coord) (ptr); 75
  76. 76. Function 语义学 1. 指向虚成员函数的指针 该指针应该能够: 含有两种数值 其数值可以被区别代表内存地址还是 virtual table 中的地址 float (Point::*pmf) () = &Point::z() ; Point *ptr = new Point3d; ptr->z () // 多态 ( ptr -> *pmf ) (); &Point::x() ; // 如果 x 不是多态的结果 &Point::z() ; // 如果 z 是多态的结果 ( * ptr -> vptr [ (int) pmf ] ) ( ptr ); // 一种综合方法,很明显,你不能有多于 128 个虚函数 ((( int ) pmf ) & ~ 127 ) ? (*pmf) ( ptr ) : ( * ptr -> vptr [ ( int) pmf ] (ptr) ); 76
  77. 77. Function 语义学 1. 指向虚成员函数的指针(多重继承和虚继承情况) 引入结构 __mptr 后的情况 delta 用于表示 this 和 vptr 的偏移值,用于多重继承和虚继承; index 是虚函数的索引 faddr 是非虚函数的地址 v_offset 是基类的 vptr 如果 vptr 位于对象的首部, delta 就没有必要 struct __mptr { ( ptr -> *pmf ) (); int delta; ( pmf.index < 0 ) ? int index; ( *pmf.faddr ) ( ptr + pmf.delta ) : union { ( * ptr -> vptr [pmf.index] (ptr) ); ptr2func faddr; int v_offset; }; }; 77
  78. 78. Function 语义学 1. inline 函数 inline 函数何时展开 1 :分析函数定义,决定是否可以进行展开 2 :在展开点上决定能否展开,需要引入参数求值和临时对象管理 对形式参数的管理 inline int min (int I, int j) { return i<j ? I : j ; } minval= val1< val2 ? val1 : val2 ; int minval; int val1=1024; int val2=2048; minval=1024; minval=min ( val1, val2 ); int t1; int t2; minval=min (1024, 2048); minval= (t1=foo()), (t2 = bar() +1 ), t1<t2 ? t1: t2; minval=min ( foo(), bar() + 1); 78
  79. 79. Function 语义学 1. inline 函数 局部变量管理 inline int min (int I, int j) { int minval= i<j ? I : j ; return minval; int __min_lv_minval; } minval= (__min_lv_minval = int minval; val1<val2 ? val1 : val2 ), int val1=1024; int val2=2048; __min_lv_minval minval=min ( val1, val2 ); 79
  80. 80. 构造、析构和拷贝的语义 1. 无继承情况下对象的构造 由于 Point 的构造、析构函数都是 trivial , global 在程序的开始和结束都不会调用相应的构 造、析构函数 同理, local 的缺省构造、析构函数都没有被调用 typedef struct { 关于 heap 的 new 、 delete 操作,会展开 float x,y,z; *heap=local 会出现一个警告 } Point; warning, line 7: local is used before being initialized. Point global; Point foobar () { Point local; Point * heap= new Point; Point *heap=__new ( sizeof ( Point) ); __delete ( heap ); *heap=local; delete heap; return local; } 80
  81. 81. 构造、析构和拷贝的语义 1. 无继承情况下对象的构造(带构造函数) global 在程序的开始时( startup )会调用相应的构造 local 的构造函数被调用,同时,它是个 inline 函数,会被展开 关于 heap 的 new 、 delete 操作,会展开 class Point { public: Point(float x=0.0 … ) : _x(x), …; } Point global; Point foobar () Point local; { local._x = 0.0 ; … … Point local; Point * heap= new Point; *heap=local; Point *heap = __ new ( sizeof ( Point ) ) ; delete heap; if ( heap ! = 0 ) return local; heap -> Point:: Point (); } 81
  82. 82. 构造、析构和拷贝的语义 1. 带虚函数情况下对象的构造 Point* Point::Point ( Point* this, float x, float y ) 构造函数会被附加一些代码,用于初始化 vptr : _x(x), _y(y) { 合成一个拷贝构造函数和一个赋值演算符 展开 this->__vptr_Point = __vtbl_Point; this->_x=x; this->_y=y; class Point { return this; public: } Point(float x=0.0 … ) : _x(x), …{} ; inline Point* virtual float z (); Point::Point ( Point* this, const Point& rhs) { protected: this->__vptr_Point = __vtbl_Point; float _x, _y; //memcpy 拷贝成员 } return this; Point global; local 的构造和 new ) Point foobar () 操作以后的初始化没 { 变 Point local; Point * heap= new Point; heap->Point::Point (heap, local) ; *heap=local; delete heap; return local; } 82
  83. 83. 构造、析构和拷贝的语义 1. 继承情况下对象的构造函数 T object; 记录在成员初始化列表中的初始化超作 带有非 trivial 构造函数,但没有出现在初始化 Object::Object 列表的初始化工作 设置 offset 在此之前,如果对象有 vptr ,必须初始化 vptr (可能有多个) this->classVirtualFather ( vfx ) 在此之前,需要调用对象的父类的构造函数,以 ( this->vfz ).ClassZ::ClassZ () 父类的声明循序一次执行以下操作(可以多次) 调整 this 指针 传递参数给父类的初始化列表 父类带有非 trivial 构造函数,但没有出现在初始 this->classFather ( fx ) 化列表的初始化工作 ( this->fz ).ClassZ::ClassZ () 如果父类是多重继承下非第一个基类,需要 调整 this 指针 this->__vptr__classX = __vtbl__classX 在此之前,说有的虚基类的构造函数必须被调 _y ( y ); Vertex *next; 用,从左到右,从最深到最浅 Point2d *pPoint2d z.ClassZ::ClassZ(); __vptr_Vertex 传递参数给虚基类的初始化列表,非 trivial float z; 构造函数,但没有出现在初始化列表的初 Point2d *pPoint2d 始化工作 __vptr_Point3d 对象的每一个虚基类的偏移量必须在执行期 float mumble; float x; 有效 float y; 如果对象是最底层的类,它的构造函数可能 __vptr_Point2d 被调用 83
  84. 84. C++ 内存管理 1. 程序的运行环境 主要问题:由于程序执行时,源程序正文中同样的名字可以表示目标机器中不同的数据对象 ,所以需要考察名字和数据对象之间的关系 活动:过程(函数、方法)的每次执行称为该过程的一个活动 活动树(单线程) •假设 1 :控制流连续 •假设 1 :过程(函数)从过程体的起点开始,最后控制返回本次调用点之后 •多线程环境下,每一个线程有线程的活动树 84
  85. 85. C++ 内存管理 1. m m m m m 活动树 q(1,9) r q(1,9) q(1,9) 名字和数据对象之间的绑定 q(1,3) q(1,3) 活动树和栈 q(2,3) int a[1024]; p(2,3) dwMix=4 int readArray() { } int partition(int begin,int end) { } void quicksort(int begin,int end) { m int dwMid=partition(begin,end); quicksort(begin,dwMid-1); r q(1,9) quicksort(dwMid+1,end); p(1,9) q(1,3) q(5,9) } p(1,3) q(1,0) q(2,3) p(5,9) q(5,5) q(7,9) int main() { int dwEnd=readArray(); p(2,3) q(2,2) q(3,3) p(7,9) q(7,7) q(9,9) quicksort(0,dwEnd); } dwMix=1 dwMix=8 85
  86. 86. C++ 内存管理 1. 运行环境的存储组织 代码 运行时内存中都有些什么 •目标代码 静态数据 •数据对象 栈 •记录活动过程的控制栈的对应物 代码区、数据区、栈和堆 (请区分栈和堆 ) 返回值 活动记录(栈存放的内容) 实参 •返回值 •实参 控制链(optional) 堆 •控制链和访问链( optional ) •机器状态 访问链(optional) •局部数据 机器状态 •临时数据 局部数据 临时数据 86
  87. 87. C++ 内存管理 1. 静态分配 代码 何为静态分配 静态数据 静态分配的局限性 •数据对象和长度在编译时必须确定 栈 •不允许递归 •数据结构不能动态建立 代码 C++ 中的静态分配 静态数据 •全局变量 栈 •局部静态变量 堆 •类的静态成员变量 堆 87
  88. 88. C++ 内存管理 1. 静态分配 代码 何为静态分配 静态数据 静态分配的局限性 •数据对象和长度在编译时必须确定 栈 •不允许递归 代码 •数据结构不能动态建立 C++ 中的静态分配 静态数据 •全局变量 栈 •局部静态变量 堆 •类的静态成员变量 堆 88
  89. 89. C++ 内存管理 1. 栈式分配策略 代码 何为栈式分配 C++ 中的栈式分配 静态数据 •局部变量(自动变量) 代码 栈 悬空引用 静态数据 栈式分配的局限性 栈 •活动结束时必须保持局部名字的值 •被调用者活动比调用者的活动的生存期长 堆 char* getDateString() { void proc() { 堆 char szBuf[32]; char *p=getDateString(); time_t timeNow; char *q=getDateString(); time(&timeNow); //do something about p strftime(szBuf,32,quot;%Y%m%dquot;, } proc localtime(&timeNow)); proc return szBuf; getDateString } 89
  90. 90. C++ 内存管理 1. 堆式分配策略 代码 何为堆式分配 垃圾单元 静态数据 悬空引用 栈 C++ 中的堆式分配 •malloc 和 free 代码 •new 和 delete •new[] 和 delete[] 静态数据 栈 堆 堆 90
  91. 91. C++ 内存管理 1. C++ 内存管理要点 把握变量的生存期 •数据段变量(全局变量和局部静态变量)的生存期(多线程条件下的互斥访问) •栈变量(局部变量)的生存期 •堆变量的生存期 malloc 不会调用相应的构造函数 使用相同形式的 new 、 delete 和 new[] 、 delete[] free 不会调用相应的析构函 •new 和 malloc 的差别 数 •delete 和 free 的差别 new 、 delete 不会调用一系列的构造、析构函 数 •new 和 new[] 、 delete 和 delete[] 的差别 class demo { demo::~demo() { 在构造函数、析构函数中处理对象中的 public: if(p!=NULL) free(p); 指针 demo(char*); } •在构造函数中初始化指针,如果是空指针,也 ~demo(); 需要明确初始化(参考“ C++ 构造函数”部分 的讨论) private: int main() { •赋值运算符中内存的删除、重新配置 char* p; demo cDemo1(quot;Hello •析构函数中删除指针(删除空指针是安全的) worldnquot;); }; 如果类中有指针,请声明类的拷贝构造 demo cDemo2(cDemo1); demo::demo(char* pSrc) { 函数和赋值运算符 return 0; if(pSrc==NULL) p=NULL; •拷贝构造函数和赋值运算符的缺省情况—按位 } else p=strdup(pSrc); 拷贝 •指针按位拷贝以后,两个指针指向同一块内存 } 91
  92. 92. C++ 内存管理 1. C++ 内存管理要点 分配不到内存时的行为 #include <iostream> •malloc 分配不到内存,返回 NULL #include <new> •new 不到内存,一些编译器是返回 NULL #include <stdlib.h> •new 不到内存,一些编译器是抛出异常 std::bad_alloc using namespace std; 当 new 操作分配不到内存时,将调用 <new> 中 void noMoreMemory () { 的 set_new_handler 中指定的处理函数 cerr<<“ERROR: NO MEMORYn”; 大部分编译器目前缺省处理方式是抛出异常 std::bad_alloc abort(); } int main () { set_new_handler (noMoreMemory ); int* p=new int [0xEFFFFFFF]; return 0; } 92
  93. 93. C++ 内存管理 1. C++ 内存管理要点 void * operator new ( size_t size ) { if (size == 0 ) size = 1; 如何编写自己的 new 和 delete while ( true ) { 继承情况下的 new 及其对策 试图分配内存 delete 也需要判断相应的情况,其操作 if ( 分配成功 ) return ( 相应的指针 ); 就是删除相应的空间 new_handler gHd =set_new_handler (0); C++ 规范规定,用户要求 0 字节的内 set_new_handler ( gHd ); 存, new 也必须返回一个正确指针 if (gHd) { (*gHd )() } ; 寻找缺省的 new_handler 的地址 else throw std::bad_alloc(); } 调用缺省的 new_handler ,如果返回 为空,抛出异常 } class Base { void* Base::operator new (size_t size ) public: { static void * operator new ( size_t size ); { if (size != sizeof ( Base ) ) …… return ::operator new (size ); } …… class Derived : public Base { … }; } Derived *p= new Derived ; 93
  94. 94. C++ 内存管理 1. C++ 内存管理要点 如果编写了一个操作符 new ,请再写一个 delete ,保证系统的一些假设 异常情况下的内存释放和 auto_ptr 数据,用于记录 int *p=new int; 内存的大小 p int需要的内存 ( 4个字节) 94
  95. 95. C++ 内存管理 1. C++ 的数组 一维数组的定义、元素引用 [] 实际是变址运算符, a+i 就是 a[i] 的地址, *(a+i) 是 a+i 指向的数组元素 +i 在地址上等于偏移 sizeof( 数组元素 )*i [] 的变址运算符要求 C++ 数组的元素大小必须一致,即类型一致 C++ 的多维数组 •多维数组的定义、元素引用 列优先时, a[i] •C++ 的多维数组采用行优先的存储方式 [j] 的意义应该 •a[i][j] , a[i] 和 a 的意义, *(a+i) , *(a+i)+j 和 *(*(a+i)+j) 的意义 是 *(*(a+j)+i) a[0][0] a[1][1] *(a[0]+0)=*(a[0]) *(a[1]+1) *(*(a+0)+0)=**a *(*(a+1)+1) a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] *(a[1]+0)=*(a[1]) a[1][0] a[1][1] a[1][2] a[1][3] *(*(a+1)+0)=**(a+1) a[2][0] a[2][1] a[2][2] a[2][3] a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] Purify 的不能检测这种错 a[1][-1] // 指针减 误( ABR,ABW 错误) 法 95
  96. 96. C++ 内存管理 1. C++ 的数组 int a[1024]; a[21]=121; 一维数组的定义、元素引用 int c=a[21]; [] 实际是变址运算符, a+i 就是 a[i] 的地址, *(a+i) 是 a+i 指向的数组元素 c=*(a+21); +i 在地址上等于偏移 sizeof( 数组元素 )*i [] 的变址运算符要求 C++ 数组的元素大小必须一致,即类型一致 int b[3][3]; b[2][4]=123; C++ 的多维数组 c=b[2][4]; •多维数组的定义、元素引用 •C++ 的多维数组采用行优先的存储方式 列优先时, a[i] •a[i][j] , a[i] 和 a 的意义, *(a+i) , *(a+i)+j 和 *(*(a+i)+j) 的意义 [j] 的意义应该 •a[i][j][k] 和 *(*(*(a+i)+j)+k) a[1][1] a[0][0] 是 *(*(a+j)+i) *(a[1]+1) *(a[0]+0)=*(a[0]) *(*(a+1)+1) *(*(a+0)+0)=**a a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][0] a[1][1] a[1][2] a[1][3] *(a[1]+0)=*(a[1]) a[2][0] a[2][1] a[2][2] a[2][3] *(*(a+1)+0)=**(a+1) a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] Purify 的不能检测这种错 a[1][-1] // 指针减 误( ABR,ABW 错误) 法 96
  97. 97. C++ 内存管理 1. purify Purify 是 IBM 的产品,可以自动查明应用程序中难以发现的运行时错误。 Purify 采用 “目标代码插入”技术,可以检查出库、组件中的运行时错误。 注意: Purify 的使用将导致软件性能的急剧下降。 注意: Purify 的使用可能掩盖一些 bug 。 Purify 的使用方法 •purify [-option ...] $(Linker) $(CFLAGS) -o program $(OBJS) $(LIBS) •执行相应的程序 一些主要的 options •-log-file •-chain-length •-threads •-max-threads 97
  98. 98. C++ 内存管理 1. Purify 能够检查以下错误(或提供信息) ABR Array bounds read WARNING ABW Array bounds write CORRUPTING BRK Brk or sbrk called outside malloc CORRUPTING BSR Beyond stack read WARNING BSW Beyond stack write WARNING COR Fatal core dump FATAL FIU File descriptor in use INFORMATIONAL FMM Freeing mismatched memory WARNING FMR Free memory read WARNING FMW Free memory write CORRUPTING 98
  99. 99. C++ 内存管理 1. Purify 能够检查以下错误(或提供信息) FNH Freeing non-heap memory CORRUPTING FUM Freeing unallocated memory CORRUPTING IPR Invalid pointer read FATAL IPW Invalid pointer write FATAL MAF Memory allocation failed INFORMATIONAL MIU Memory in use INFORMATIONAL MLK Memory leak WARNING MRE Malloc re-entered CORRUPTING MSE Memory segment error WARNING NPR Null pointer read FATAL 99
  100. 100. C++ 内存管理 1. Purify 能够检查以下错误(或提供信息) NPW Null pointer write FATAL PAR Bad function parameter WARNING PLK Potential memory leak WARNING SBR Stack array boundary read WARNING SBW Stack array boundary write CORRUPTING SIG Fatal signal handled INFORMATIONAL SOF Stack overflow error WARNING UMC Uninitialized memory copy WARNING UMR Uninitialized memory read WARNING PMR Partial uninitialized memory read WARNING 100
  101. 101. C++ 内存管理 1. Purify 能够检查以下错误(或提供信息) WPR Watchpoint read INFORMATIONAL WPW Watchpoint write INFORMATIONAL WPF Watchpoint free INFORMATIONAL WPM Watchpoint malloc INFORMATIONAL WPN Watchpoint function entry INFORMATIONAL WPX Watchpoint function exit INFORMATIONAL ZPR Zero page read FATAL ZPW Zero page write FATAL 101
  102. 102. C++ 内存管理 1. Quantify 和 PureCoverage Quantify 主要解决软件开发过程中的性能问题。它给开发团队提供了一个性能数据的全局图 形化视图,使用户从开发流程的开头起就注重性能问题,确保做到更快地发布更好的软件。 PureCoverage 提供应用程序的测试覆盖率信息。在软件开发过程中能自动找出代码中未经 测试的代码,保证代码测试覆盖率。它还能针对每次测试生成全面的覆盖率报告,归并程序 多次运行所生成的覆盖数据,并自动比较测试结果,以评估测试进度。 102
  103. 103. 1. Effective C++ 改变旧有的 C 习惯 尽量以 const 和 inline 取代 #define (参考 const 和 inline 的介绍) 尽量以 <iostream> 取代 <stdio.h> 由于公司的代码包含大量 C 的日志输出,请不要使用该特征,特别是在遗留系 统中。 iostream 和 stdio 使用不同的输出缓冲区,在某种情况下会出现输出顺序和在程序 中的输出顺序不一样 尽量以 new 和 delete 代替 malloc 和 free (参考内存管理部分) 尽量使用 C++ 风格的注释 if (a>b) { //int temp=a; //swap a & b //a=b; //b=temp; } if (a>b) { /*int temp=a; /* swap a & b */ a=b; b=temp; */ } 103
  104. 104. 1. Effective C++ 内存管理 使用相同形式的 new 和 delete (参考内存管理部分) 记得在 destructor 中以 delete 对付 pointer members (参考内存管理部分) 为内存不足的状况预做准备(参考内存管理部分) 撰写 operator new 和 operator delete 时应遵行公约(参考内存管理部分) 避免遮掩了 new 的正规形式 如果你写了一个 operator new ,请对应也写一个 operator delete (参考内存管理部分) class X { public: void* operator new (size_t size); void* operator new (size_t size, new_handler p); }; class X { public: void* operator new (size_t size, new_handler p); }; 104
  105. 105. 1. Effective C++ 构造函数、析构函数和 Assignment 运算符 如果类内动态配置有内存,请为此类声明一个拷贝构造函数和一个赋值运算符(参考内存管 理部分) 在构造函数中尽量以初始化列表取代赋值 •对于对象的 const 成员和引用成员,只能利用初始化列表进行初始化 •初始化列表的效率比赋值高(在继承的情况下和在定义为全局变量的情况下) 初始化列表中的成员初始化排列次序应该和其在类中的声明次序相同 •构造函数使用初始化成员初始化时,是根据它在类中的定义来进行的 class Array { 总是让基类拥有虚析构函数 •子类会主动调用父类的构造函数,但析构函数 public: 没有这样的机制 Array(int lowBound, int highBound); •规则:当类中有一个虚函数的时候,就必须将 private: 析构函数声明为虚的 •纯虚类的析构函数必须定义 size_t size; int lBound,hBound; class AWOV { }; public: virtual ~AWOV () = 0; Array:: Array(int lowBound, int highBound) }; : size(highBound-lowBound+1), AWOV::AWOV () {}; lBound(lowBound),hBound(highBound) 105
  106. 106. 1. Effective C++ 构造函数、析构函数和 Assignment 运算符 令 operator= 传回 *this 的引用 x=y=z=“Hello”; •assignment 运算符是右结合 x=(y=(z =“Hello”)); •C& C :: operator= (const C&) x.operator=(y.operator=(z.operator=“Hello”)); •如果返回 void ,将导致赋值动作链失败 (x=y)=z=“Hello”; •如果返回 const & ,将导致第 4 行的赋值失败 •参数 const C 是为了保证不能对 = 运算符的右侧进 String& String::operator = ( String& rhs ) 行修改(符合最一般的语义) String x=“Hello”; const String temp=(“Hello”); x = temp; 106
  107. 107. 1. Effective C++ 构造函数、析构函数和 Assignment 运算符 在 operator= 中为所有的数据成员赋予内容 class Base { Derived& Derived::operator= (const Derived& rhs) { public: if ( this = &rhs ) return *this; Base ( int init = 0 ): x(init) {}; y = rhs.y; private: return *this; int x; } }; class Derived : public Base { Derived& Derived::operator= (const Derived& rhs) { public: if ( this = &rhs ) return *this; Derived ( int init ) : Base (init), y (init) {}; static_cast<Base&>(*this) = rhs; Derived& operator= (const Derived& rhs); y = rhs.y; private: return *this; int y; } }; void test() { Derivted d1(0); //d1.x=0; d1.y=0 Derivted d2(1); //d1.x=1; d1.y=1 d1=d2; // d1.x=0; d1.y=1 } 107
  108. 108. 1. Effective C++ 构造函数、析构函数和 Assignment 运算符 在 operator= 中检查是否 quot; 自己赋值给自己 quot; class String { public: String (const char* value); ~String (); String& operator = (const String& rhs); private: char *data }; String& String::operator = (const String& rhs){ delete [] data; data = new char [ strlen(rhs.data) + 1]; strcpy(data,rhs.data); return *this; } String a=“Hello”; a=a; 108

×