现在的位置: 首页 > 综合 > 正文

深入探索C++对象模型 第二章 构造函数语意学

2018年04月13日 ⁄ 综合 ⁄ 共 4866字 ⁄ 字号 评论关闭

1.   Jerry
Schwarz
iostream函数库建构师,曾为了让cin能够求得一个真假值,于是他为它定义了一个conversion运算符operator
int()
。但在语句cin << intVal中,其行为出乎意料:程序原本要的是cout而不是cin!但是编译器却找到一个正确的诠释:将cin转型为整型,现在left
shift operator <<
就可以工作了!这就是所谓的“Schwarz
Error
”。Jerry最后以operator
void *()
取代operator int()

 

2.   引入关键词explicit的目的,就是为了提供程序员一种方法,使他们能够制止单一参数的constructor被当作一个conversion运算符。其引入是明智的,但其测试应该是残酷的!

2.1 Default Constructor的建构操作

1.   global
objects
的内存保证会在程序激活的时候被清为0local
objects
配置于程序的堆栈中,heap objects配置于自由空间中,都不一定会被清为0,它们的内容将是内存上次被使用后的遗迹。

 

2.   在各个不同的版本模块中,编译器避免合成出多个default
constructor
的方法:把合成的default constructorcopy
constructor
assignment copy operator都以inline方式完成。一个inline函数有静态链接,不会被档案以外者看到。如果函数过于复杂,不适合做成inline,就会合成一个explicit
non-inline static
实体。

 

3.   以下四种情况,编译器必须为未声明constructorclasses合成一个implicit
nontrivial default constructor
:带有default
constructor
member class object,带有default
constructor
base class,带有virtual
function
,带有virtual base class。其它各种情况且没有声明任何constructorclasses,它们拥有的是implicit
trival default constructors
,它们实际上并不会被合成出来。

 

4.   编译器合成implicit
nontrivial default constructor
,不过是暗地里作了一些重要的事情以保证程序正确合理地运行。如果程序员提供了多个constructors,但其中都没有default
constructor
,编译器同样会在这些constructors中插入一些相同功能的代码,这些代码都将被安插在explicit
user code
之前。

2.2 Copy Constructor的建构操作

1.   有三种情况,会以一个class的内容作为另一个class
object
的初值:

ü          对一个object作明确的初始化操作,如:someClass
obt = obtb;

ü          一个object被当作参数交给某个函数时,如:foo(obt);

ü          当函数返回一个class
object
时。

class设计者明确定义了一个copy
constructor
,大部分情况下,该constructor会被调用。这可能导致一个暂时性的class
object
的产生或程序代码的蜕变,或者两者皆有。

 

2.   如果class没有提供一个explicit
copy constructor
,当class object以相同class的另一个object作为初值时,其内部是以所谓的default
memberwise initialization
手法完成的,即把每一个内建的或派生的data
member
的值,从一个object拷贝到另一个object。不过,它并不会拷贝其中的member
class object
,而是以递归的方式施行memberwise
initialization

 

3.   一个class
object
可以从两种方式复制得到:初始化和指定,从概念上而言,这两个操作分别是以copy
constructor
copy assignment operator完成的。

 

4.   如果class没有声明一个copy
constructor
,就会有隐含的声明implicitly
declared
或隐含的定义implicitly defined出现。C++copy
constructor
分为trivialnontrivial两种。只有nontrivial的实体才会被合成出来。决定一个copy
constructor
是否为trivial的标准在于class是否展现出所谓的“bitwise
copy semantics
”。

 

5.   以下四种情况,一个class不展现bitwise
copy semantics

ü          class内含一个member
object
而后者的class声明有或被编译器合成有一个copy
constructor
时;

ü          class继承自一个base
class
而后者存在或被编译器合成有一个copy constructor时;

ü          class声明了一个或多个virtual
functions
时;

ü          class派生自一个继承串链,其中有一个或多个virtual
base classes
时。

前两种情况中,编译器必须将memberbase
class
copy constructors调用操作安插到被合成的copy
constructor
中。

 

6.   一旦一个class
object
中必须引入vptr,编译器就必须为它的vptr正确地设置好初值。此时,该class就不再展现bitwise
semantics

7.   当一个base
class object
以其derived class object内容作初始化操作时,其vptr复制操作必须保证安全。

8.   每一个编译器对于虚拟继承的承诺,都表示必须让derived
class object
中的virtual base class
subobject
的位置在执行期准备妥当。维护位置的完整性是编译器的责任。

2.3 程序转化语意学

1.   每一个明确的初始化操作都会有两个必要的程序转化阶段:先重写每一个定义,剥除其中的初始化操作,然后安插classcopy
constructor
调用操作。

2.   把一个class
object
当作参数传给一个函数或是作为一个函数的返回值,相当于以下形式的初始化操作:

X xx = arg; 其中xx代表形式参数或返回值,而arg代表真正的参数值。

应该将形参从原先的类的对象改变为类的引用,避免复制

3.   函数定义如下:X
bar(){X xx; return xx;}
bar()的返回值通过一个双阶转化从局部对象xx中拷贝出来:

ü          首先为bar添加一个额外参数,类型是class
object
的一个reference
,这个参数用来放置被拷贝构建而得的返回值

ü          然后在return指令之前安插一个copy
constructor
调用操作,以便将欲传回之object的内容当作上述新增参数的初值,同时重写函数使它不返回任何值。

 

4.   Named
Return Value(NRV)
优化如今被视为是标准C++编译器的一个义不容辞的优化操作,它的特点是直接操作新添加的额外参数。注意只有copy
constructor
的出现才会激活C++编译器的NRV优化!NRV优化虽然极大地改善了效率,但还是饱受批评:一是优化由编译器默默完成,而是否完成以及其完成程度完全透明;二是一旦函数变得比较复杂,优化就变得较难施行;三是优化由可能使程序产生错误——有时并不是对称地调用constructordestructor,而是copy
constructor
未被调用!

 

5.   在编译器提供NRV优化的前提下,如果可以预见class需要大量的memberwise初始化操作,比如以by
value
的方式传回objects,那么提供一个explicit
inline copy constructor
的函数实体就非常合理。此种情况下,没有必要同时提供explicit
assignment operator
定义。

 

6.   copy
constructor
的应用迫使编译器多多少少对程序代码作部分优化,尤其是当一个函数以by
value
的方式传回一个class object,而该class有一个copy
constructor
(或定义或合成)时,无论在函数的定义还是在使用上将导致深奥的程序转化。此外,编译器将实施NRV优化。

 

7.   注意正确使用memset()memcpy(),它们都只有在classes不含任何由编译器产生的内部membersvptr时才能有效运行!

2.4 成员初始化列表

1.   当写下一个constructor时,就有机会设定class
members
的初值。不是经由member initialization
list
,就是在constructor函数本身之内。

2.   下列情况,为了让程序能被顺利编译,必须使用member
initialization list

ü          初始化一个reference
member
时;

ü          初始化一个const
member
时;

ü          调用一个base
class
constructor,而它拥有一组参数时;

ü          调用一个member
class
constructor,而它拥有一组参数时。

3.   编译器会对initialization
list
一一处理并可能重新排序,以反映出members的声明次序,它会安插一些代码到constructor内,并置于任何explicit
user code
之前。

4.在对成员进行初始化时使用成员函数,这会导致一个模凌两可的问题,因为你不知道成员函数对于类的对象的依赖有多大,当其依赖于类的对象时,则会导致错误,因此,在构造函数内部调用成员函数对成员进行初始化,而在初始化列表中对成员也初始化但不是用成员函数,   一个忠告:请使用“存在于constructor体内的一个member”,而不是“存在于member
initialization list
中的一个member”,来为另一个member设定初值


1.如果某个类中有使用其他定义了构造函数的类的对象那么,在此类中如果没有定义构造函数,则编译器会合成默认构造函数,这个构造函数会能够调用其他类的构造函数来处理这个对象,但是它不能初始化自身类中的成员,给类中的数据成员初始化的工作应该是程序员的任务,而如果程序员定义了一个构造函数,其中对数据成员进行了初始化,然而没有调用其他类的构造函数来对对象进行初始化,则编译器会在程序员定义的构造函数中加入初始化其他对象的代码,而当有多个其他类对象时,声明顺序为对象在类中的顺序

2.有四种情况下编译器会构造默认构造函数:

a.带有默认构造函数的类成员对象,即某个类中有其他类的对象

b.带有默认构造函数的基类:子类的对象初始化会调用基类的构造函数,这时候基类如果没有构造函数,则会产生一个默认构造函数

c.带有虚函数的类:默认构造函数会参数虚函数列表和指向虚函数列表的指针

d.带有虚基类的类

复制构造函数在相同的类型下也会由编译器生成复制构造函数,有其他类成员对象,而这个其他类有复制构造函数;继承了一个又复制构造函数的类;有虚函数;虚继承;

3.在对基类的对象初始化时,用子类的对象,如果不是基类对象的引用和指针,则初始化时虚函数列表只会复制基类的部分给基类对象,而如果是给基类对象和指针赋值则会是整个虚函数列表

抱歉!评论已关闭.