Inside the C++ Object Model 深度探索对象模型 5-7

 

5构造,解构,拷贝语意学 Semantics of Construction, Destruction, Copy

纯虚拟函数的存在 Presence of Pure VF

>pure virtual function可以被定义和调用invoke: 只能被静态调用statically, 不能经由虚拟机调用; Ex. inline void Abstract_base::interface() const {...} Abstract_base::interface();

>pure virtual destructor一定要定义, 每一个derived class destructor会被编译器扩展, 以静态方式调用每一个virtual base class以及上一层base class的destructor; 缺少base class destructor的定义会导致链结失败;

>继承体系中的每一个class object的destructor都会被调用; 建议-不要把virtual destructor声明为pure;

虚拟规格的存在 Virtual Specification

>一般而言把所有的成员函数都声明为virtual function, 再靠编译器的优化操作把非必要的virtual invocation去除, 不是好的设计观念; Ex. inline函数的优化被破坏;

虚拟规格中const的存在

>决定一个virtual function是否const需要考虑到subclass的函数调用(const reference, const pointer), 以及derived instance修改data member的可能性;

重新考虑class的声明

1
2
3
4
5
6
7
8
9
class  Abstract_base             
{             
public :             
     virtual  ~Abstract_base() = 0;             
     virtual  void  interface()  const  = 0;             
     virtual  const  char * mumble()  const  return  _mumble;}             
protected :             
     char * _mumble;             
};
1
2
3
4
5
6
7
8
9
10
class  Abstract_base_update             
{             
public :             
     virtual  ~Abstract_base_update();  // not pure virtual any more             
     virtual  void  interface() = 0;  // not const any more             
     const  char * mumble()  const  return  _mumble;}  // not virtual             
protected :             
     Abstract_base_update( char * pc = 0);  // add a contructor with parameter             
     char * _mumble;             
};

5.1 无继承情况下的对象构造

>在C中global被视为一个临时性的定义, 放在data segment中的BBS空间, Block Started by Symbol; C++不支持临时性的定义, 所有的全局对象被当作"初始化过的数据";

抽象数据类型

>Explict initialization list 比较高效 Ex. Point pt = {1.0, 1.0, 1.0}; 
三项缺点 1) class members必须为public; 2) 只能指定常量, 因为它们在编译时期就可以被评估求值evaluated; 3) 初始化行为的失败可能性较高;

为继承做准备

>virtual function的引入使每个Object拥有一个vtbl ptr, 这个指针提供了virtual接口的弹性, 代价是每一个object需要一个额外的word空间; 编译器也会对class作出膨胀的转化;
-Constructor增加vptr的初始化, 这些代码在base class constructor之后, 在程序员代码之前调用; Ex. this->__vptr_Point = __vtbl__Point;
-合成一个copyy constrctor和一个copy assignment operator.

>如果遇到函数需要以传值by value的方式传回一个local class object; Ex. T operator+(const T&, const T&) { T result; ... return result;} 提供一个copy constructor是比较合理的, 即使default memberwise语意已经足够, 也能触发NRV优化 name return value;

5.2 继承体系下的对象构造

>Constructor可能内带大量的隐藏代码, 编译器扩充每一个constructor, 扩充程度视class的继承体系而定; 
1) 记录在member initialization list中的data members初始化操作会被放进constructor, 以members的声明顺序为序; 2)如果有member没有出现在member initialization list中, 但它有一个default constructor, 该default constructor必须被调用; 3) 在那之前, class object有virtual table points, 必须被设定初值; 4) 在那之前, 所有上层的base class constructor必须被调用, 以base class的声明顺序为序; 5) 在那之前, virtual base class constructors必须被调用, 左到右, 深到浅;

>member class objects的destructor调用次序和构造次序相反(子类->基类);

虚拟继承 Virtual Inheritance

>由最底层most derived的class来负责完成被共享的base class的subobject的构造;

Vptr初始化语意学

>Constructors的调用顺序是: 由根源到末端 bottom up, 由内到外 inside out;

5.3 对象复制语意学 Object Copy Semantic

>防止将一个calss object指定给另一个class object: 将copy assignment operator声明为private, 定义为空(member function和class的friend可以调用private的operator, 当它们企图影响拷贝时, 程序在链接时会失败--和链接器有关, 不一定都失败...)

>对于简单的类结构(Ex. Point), 如果我们不提供copy assignment operator或copy constructor, 编译器也不会产生出实体. 由于class有了bitwise copy语意, implicit copy被视为无用处的(trivial);

>class对于默认的copy assignment operator, 不会表现出bitwise copy的情况
1)当class内带一个member object, 而其class有一个copy assignment operator; 2)当一个class的base class有一个copy assignment operator; 3)当一个class声明了任何virtual functions(derived class object的vptr地址是相对的); 4) 当class继承自一个virtual base class;

>C++ Standrand Section 12.8: 我们并没有规定哪些代表virtual base class的subobject是否该被隐喻定义(implicitly defined)的copy assignment operator指派(赋值 assign)内容一次以上;

>建议: 尽可能不要允许一个virtual base class的拷贝操作; 其他建议: 不要在任何virtual base class中声明数据;

5.4 对象的功能 Object Efficiency

>bitwise copy拥有最好的效率, 一旦引入virtual继承, class不再允许bitwise copy语意, 合成型的inline copy constructor和copy assignment operator大大降低效率;

5.5 解构语意学 Semantics of Destruction

>如果class没有定义destructor, 那只有在class内带的member object或calss自己的base class拥有destructor的情况下, 编译器才会自动合成, 否则destructor或被视为不需要;

>由程序员定义的destructor被扩展的方式类似constructor被扩展的方式, 顺序相反 
1)如果object内带一个vptr, 重设相关的vtbl; 2)destructor的函数本身被执行, vptr在程序员的代码执行前被reset; 3)如果class拥有member objects, 而且拥有destructors, 它们会以声明顺序的反向顺序被调用; 4)如果有任何直接的上一层/nonvirtual base class拥有destructor, 它们会以声明顺序的相反顺序被调用; 5)如果有任何virtual base classes拥有destructor, 而且此class是最尾端most-derived的, 它们会以原来的构造顺序反向被调用;

---Section5 End---


6执行期语意学 Runtime Semantics

6.1 对象的构造和解构

>一般而言会把object尽可能放置在被使用的那个程序段附近(在使用时才定义, 如果程序在之前返回了, 可以避免产生对象), 这样可以节省不必要的对象产生和销毁的操作;  

全局对象 Global Objects

>静态的初始化和内存释放操作; 在main()用到global之前构造, 在main()结束之前销毁;

>C++程序中所有的global objects都被放置在程序的data segment中. 如果指定值, object将以此作为初值, 否则object所配置到的内容为0;(C不会自动设定初值, C语言中global object只能被常量表达式设定初值)

局部静态对象 Local Static Object

>construct和destruct只能被调用一次;

对象数组 Array of Objects

>1)配置内存给N个连续的Objects; 2)Constructor和Destructor施行与N个对象上;

void* vec_new(void* array, size_t elem_size, int elem_count, void (*constructor)(void*), void (*destructor)(void*, char))

void* vec_delete(void* array, size_t elem_size, int elem_count, void (*destructor)(void*, chhar))

Ex. Point knots[10]; vec_new(&knots, sizeof(Point), 10, &Point::Point, 0); //没有定义destruction

Default Constructor和数组

>取得constructor的地址后, 它不能成为inline;

6.2 new和delete运算符

>int *pi = new int(5); 1)通过new运算符事体配置内存: int *pi = __new(sizeof(int)); 2)设立初值: *pi = 5;

>初始化操作应该在内存配置成功后才执行: int *pi; if (pi = __new(sizeof(int))) *pi = 5; delete运算符类似: if (pi != 0) __delete(pi);

>new运算符实际上是以标准的C malloc()完成, delete运算符也是以标准的C free()完成;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
extern  void * operator  new ( size_t  size)
{
     if  (size == 0)
         size = 1;
     void * last_alloc;
     while  (!(last_alloc =  malloc (size)))
     {
         if  (_new_handler)
             (*_new_handler)();
         else
             return  0;
     }
     return  last_alloc;
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
extern  void  operator  delete ( void  *ptr)
{
     if  (ptr)
         free (( char *)ptr);
}

针对数组的new语意

>Point3d* p_array = new Point3d[10]; new时指定大小;

>delete时不用指定大小: delete [] p_array; 寻找数组维度给delete运算符的效率带来很大影响, 当程序员们没有提供中括号Ex. delete p_array; 只有第一个元素会被析构, 其他的元素依然存在;

>删除指向derived class的base class指针: Point* ptr = new Point3d[10]; delete [] ptr;//只会调用Point的析构;

1
2
3
4
5
for  ( int  ix = 0; ix < elem_count; ++ix)
{
     Point3d* p = &((Point3d*)ptr)[ix];
     delete  p;
}

Placement Operator new的语意

>Operator new的指针必须指向相同类型的class或者一块新的内存, 足够容纳该类型的object; (derived class不符合)

char* arena = new char[sizeof(Point2w)]; OR Point2w* arena = new Point2w;

>一般而言Placement Operator new不支持多态polymorphism;

6.3 临时性对象 Temporary Objects

>C++ Standard允许编译器对临时性对象的产生有完全的自由度: "在某些环境下由processor产生临时性对象是有必要的, 也是比较方便的. 这样也的临时性对象由编译器来定义." "临时性对象的被销毁, 是对完整表达式full-expression求值过程中的最后一个步骤. 该完整表达式造成临时对象的产生."(C++Standard 12.2)

>凡含有表达式执行结果的临时性对象, 应该存留到object的初始化操作完成为止;

const char *progNameVersion = progName + progVersion; --> String temp; operator+(temp, progName, progVersion); progNameVersion = temp.String::operator char*(); temp.String::~String(); //指向了未定义的heap内存;

const String &space = " "; --> String temp; temp.String::String(" "); const String &space = temp; //当temp被销毁, reference也失效;

>"如果一个临时性对象被绑定与一个reference, 对象将残留, 知道被初始化的reference生命结束, 或知道临时对象的生命范畴scope结束;"

临时性对象的迷思

>反聚合 disaggregation (将临时对象拆分, 放入缓存器)
---Section6 End---


7站在对象模型的尖端 On the Cusp of the Object Model

7.1 Template

>通用程序设计(STL)的基础, 也用于属性混合或互斥mutual exclusion机制(线程同步化控制)的参数化技术;

template<class Type>

Type min(const Type &t1, const Type &t2) {...} 用法: min(1.0, 2.0); //程序吧Type绑定为double并产生min()的程序文字实体(mangling名称)

Template的具现行为 Template Instantiation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template  < class  Type>
class  Point
{
public :
     enum  Status {unallocated, normailized, };  
     Point(Type x = 0.0, Type y = 0.0, Type z = 0.0);
     ~Point();
     void  *operator  new ( size_t );
     void  operator  delete ( void *,  size_t );
private :
     static  Point<Type> *freeList;
     static  int  chunkSize;
     Type _x, _y, _z;
};

>编译器对于template class的声明不会有反应, 上述的static data members和enum还不可用;

>enum Status只能通过template Point class的某个实体来存取或操作: Point<float>::Status s;//OK; Point::Status s;//ERROR;即使两种类型抽象来说是一样的; (将enum抽出刀一个nontemplate base class中避免多份拷贝)

>Point<float>::freeList; Point<double>::freeList; 两个freeList实体; 分别与Point class的float instantiantion和double instantiation产生关联;

>Point<float> *ptr = 0; 程序没有行为发生; 指向class object的指针本身不是一个class object, 编译器不需要知道与该class有关的members数据或object布局数据;

>const Point<float> &ref = 0; reference会真的具现出Point的float实体来; -->Point<float> temporary(float(0)); const Point<float> &ref = temporary; 0是整数, 会被转化到Point<float>类型, 编译器会判断是否可行;

>const Point<float> origin; 会导致template class的具现, float instantiation的对象布局会产生出来;

>Point<float> *p = new Point<float>; 具现 1)Point template的float实例; 2)new运算符(依赖size_t); 3)default constructor;

>两种策略: 1)编译时函数具现于origin和p存在的那个文件中; 2)链接时, 编译器被辅助工具重新激活, template函数实体存放在某个文件(存储位置)中;

Template的错误报告 Error Reporting within a Template

>一般编译器对于一个template声明, 在它被实际参数实现之前, 只施行有限的错误检查(语法); 语法之外的错误只有在特定实体被定义后才会发现;

Template中的名称决议方式 Name Resolution within a Template

>区分1)Scope of the template declaration 定义出template的程序; 2)Scope of the template instantiation 具现出template的程序;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
extern  double  foo( double ); //1)
template < class  type>
class  ScopeRules
{
public :
     void  invariant() {
         _member = foo(_val);
     }
     type type_dependent(){
         return  foo(_member);
     }
private :
     int  _val;
     type _member;
};
                                                                                                                                                                                                                                                                               
extern  int  foo( int );  //2)
ScopeRules< int > sr0;

>sr0.invariant(); 调用的是foo(double)-1), _val是int类型, 属于"类型不会变动"的template class member, 函数的决议只和函数的原型signature有关, 与返回值无关; foo的调用和template参数无关, 属于scope of the template declaration, 在此scope中只有一个foo() -->1);

>sr0.type_dependent(); 类型相关, 与template参数有关, 参数将决定_member的具体类型; 属于scope of the template instantiation; 在此scope下有两个foo()声明, 由于_member的类型是int, 调用的是foo(int)-2);

>编译器必须保持两个scope contexts: 1)scope of the template declaration, 专注于一般的template class; 2)scope of the template instantiant, 专注于特定的实体; 编译器的决议resolution算法必须决定那个是适当的scope, 然后在其中搜索适当的name;

Member Function的具现行为 Member Function Instantiation

>"如果一个virtual function被具现instantiated出来, 其具现点紧跟在其class的具现点之后"

7.2 异常处理 Exception Handling

>为了维持执行速度, 编译器可以在编译时期建立起用于支持的数据结构; 程序会膨胀, 编译器可以忽略这些结构直到有exception被丢出;

>为了维护程序大小, 编译器可以在执行时期建立起用于支持的数据结构; 影响了程序的速度, 编译器只有在必要的时候才建立那些数据结构;

Exception Handling 快速检阅

>C++的exception handling组成: 1)一个throw子句, 在程序某处发出一个exception, 被丢出的exception可以是内建类型或使用者自定义类型; 2) 一个或多个catch子句, 每个catch子句都是一个exception handler. 处理某种类型的exception, 在大括号内处理程序; 3)一个try区段, 一系列的叙述句statements, 可能会引发catch子句起作用;

>推离程序unwinding the stack, 在函数被推离堆栈之前, 函数的local class objects的destructor会被调用;

对Execption Handling的支持

>对于一个exception, 编译器负责: 1)检验发生throw操作的函数; 2)决定throw操作是否发生在try区段; 3)如果是, 编译系统必须把exception type和每个catch子句比较; 4)如果比较吻合, 流程控制交到catch子句中; 5)如果throw发生不是在try区段中, 或没有catch子句吻合, 系统必须 a)销毁所有active local objects; b)从堆栈中将当前的函数unwind掉; c)进行到程序堆栈的下一个函数中去, 重复步骤 2)-5);

决定throw是否发生在try区段中

>-try区段外的区域, 没有active local objects; -try区段外的区域, 有active local objects需要解构; -try区段以内的区域;

>program counter-range表格

将exception的类型和每个catch子句的类型比较

>类型描述器type descriptor;

当一个实际对象在程序执行时被抛出

>exception object会被产生并放在在相同形式的exceptio数据堆栈中;

>在一个catch子句中对exception object的任何改变都是局部性的.

7.3 执行期类型识别 Runtime Type Identification  

>downcast向下转型有潜在的危险;

Type-Safe Downcast 保证安全的向下转型操作

>支持type-safe downcast在空间和时间上的负担 -需要额外的空间来存储类型信息type information, 通常是一个指针, 指向某个类型信息节点; -需要额外的时间以决定执行期的类型runtime type;

Type-Safe Dynamic Cast 保证安全的动态转型

>dynamic_cast; Ex. if(Derived* pD= dynamic_cast<Derived*>(pB)) ... else ...

References并不是Points

>对class指针类型施以dynamic_cast运算符, 会获得true或false; -如果传回真正的地址, 表示object的动态类型被确认; -如果传回0, 表示没有指向任何object;

>对reference施行dynamic_cast, 对于一个non-type-safe cast, 与指针的情况不同; 如果把一个reference设为0, 会引起一个临时对象产生, 初值为0, reference被设定为该临时对象的一个别名alias; -如果reference真正参考到适当的derived class, downcast会被执行; -如果reference不真正是某种derived class, 不能传回0, 丢出一个bad_cast exception;

Ex. try { Derived &rD = dynamic_cast<Derived&>(rB); ...} catch {bad_cast} {... }

Typeid运算符

>typeid运算符传回一个const reference, Ex. if (typeid(rD) == typeid(rB)) 类型为type_info; equal等号运算符是一个被overloaded的函数: bool type_info::operator==(const type_info&) const;

>type_info不只是用于多态类polymorphic class, 也适用于内建类型, 使用者自定义类型(非多态); 这些情况下type_info object是静态取得, 不是执行期取得;

7.4 效率和弹性

动态共享函数库 Dynamic Shared Libraries

>目前的C++模型中, 新版lib的class object有变更, class的大小以及每个直接(或继承的)members的偏移量offset都在编译期就固定好(虚拟继承的members除外).这样提高效率, 但在二进制binary层面影响了弹性. object布局改变, 程序必须重新编译; 没有达到"library无侵略性";

共享内存 Shared Memory

>分布式distributed, 二进制层面的对象模型, 与程序语言无关;
---Section7 End---

用户评论

暂无评论。
发表评论:

 

 

 

你可能感兴趣的:(object)