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的声明
class Abstract_base
{
public: virtual ~Abstract_base() = 0; virtual void interface() const = 0; virtual const char* mumble() const { return _mumble;}
protected: char* _mumble;
};
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()完成;
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)();elsereturn 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的析构;
for (int ix = 0; ix < elem_count; ++ix)
{Point3d* p = &((Point3d*)ptr)[ix];delete p;
}
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
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的程序;
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---