博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
第2章 构造函数语意学
阅读量:5150 次
发布时间:2019-06-13

本文共 5004 字,大约阅读时间需要 16 分钟。

问题:对于memberwise和bitwise还不是很理解,而这两个概念却非常重要。

开篇一句话:关键词explicit之所以被引入这个语言,就是为了提供给程序员一种方法,使他们能够制止“单一参数的constructor”被当做一个conversion运算符。Conversion运算符引入应该是明智的,测试应该是严酷的(确实如此,一个隐式转换很难被发现),并且在程序一出现不寻常活动第一个症状时发出疑问。

第2.1节 Default Constructor的建构操作

区分Default Constructor

一部分是由编译器在需要时候产生出来;

一部分是程序员为了保证程序正确性,程序员设计Default Constructor。

下面讨论nontrivial default constructor四种情况。

“带有 Default Constructor ”的 Member Class Object

class Foo{
public:Foo(),Foo(int)...};class Bar{
public:Foo foo;char* str;};void foo_bar(){ Bar bar;}

Bar::foo初始化时编译器责任,Bar::str初始化则是程序员责任。

因此需要自己编写constructor函数,执行顺序为编译器先调用Foo default constructor,随后执行user code

如果有多个class member object要求以“memberobjects 在class中声明次序”来调用各个constructor

“带有 Default Constructor”的Base Class

貌似没什么说的,基类构造函数要在派生类类成员构造函数之前执行。。。

“带有一个Virtual Function”的Class

讨论放在第5章以后

“带有一个Virtual Base Class”的Class

依然没有看到什么太多值得讨论地方,可能后面会有详细阐述

总结

在合成的default constructor中,只有base class subobjects和member class objects会被初始化,所有其他nonstatic data member都不会被初始化。

C++新手两个常见误解:

1.任何class如果没有定义default constructor,就会被合成一个出来。

2.编译器合成出来的default constructor会明确设定“class内每一个data member默认值”。

第2.2节 Copy Constructor的建构操作

这一节可以解答我前面的疑问,慢慢来解答

Default memberwise initialization定义

把每一个内建的或派生的data member(例如一个指针或者数组)的值,从某个object拷贝一份到另一个object身上。不过它并不拷贝其中member class object,而是以递归方式施行memberwise initialization

Bitwise Copy Semantics(位逐次拷贝)

//以下声明展现出了bitwise copy semanticsclass Word{public:    Word(const char*);    ~Word(){delete []str;}    //...private:    int cnt;    char *str;};

注意data member,均没有构造函数,这种情况下不需要合成一个default copy constructor

但是class Word变换一下data member

//以下声明未展现出了bitwise copy semanticsclass Word{public:    Word(const string&);    ~Word(){delete []str;}    //...private:    int cnt;    string str;};//string声明了一个explicit copyconstructorclass string{public:    string(const char*);    string(const string&);    ~string();};

这种情况下编译器必须合成一个copy constructor以便调用member class string object的copy constructor

不要Bitwise Copy Semantics!

1.当class内含一个member object而后者class生命有一个copy constructor时(上例就是这种情况)

2.当class继承自一个base class而后者存在一个copy constructor时

3.当class声明了一个或多个virtual function时

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

其中3和4情况比较复杂,在后面讨论

重新设定Virtual Table的指针

如果编译器对于每一个新产生的class object的vptr不能成功而正确设定初值,将导致可怕后果。因此,当编译器导入一个vptr到class之中时,该class将不再展现bitwise semantics。现在编译器需要合成出一个copy constructor,以求将vptr适当初始化。

定义两个类,ZooAnimal和Bear:

class ZooAnimal{public:    ZooAnimal();    virtual ~ZooAnimal();    virtual void animate();    virtual void draw();    //...private:    //ZooAnimal的animate()和draw()    //所需要的数据};class Bear:public ZooAnimal{public:    Bear();    void animate();    void draw();    virtual void dance();    //...private:    //Bear的animate()和draw()和dance()    //所需要的数据};

下面例子可以靠“bitwise copy semantics”完成

Bear yogi;Bear winnie=yogi;

原因是yogi和winnie是同一个class,因此根据bitwise copy semantics把yogi的vptr值拷贝给winnie的vptr是安全的

Bear yogi;ZooAnimal franny=yogi;

在这种情况下不可以把yogi的vptr赋值给franny的vptr,否则会发生错误

对于虚基类讨论放在第三章

第2.3节 程序转化语意学

明确的初始化操作

1.重写每一个定义,其中初始化操作会被剥夺。

2.class的copy constructor调用操作会被安插进去。

参数初始化

X xx;foo(xx);

实际上这是一个值传递,并不会改变对象xx,产生C++伪代码如下

//编译器产生出临时对象X __temp0;//编译器调用copy constructor__temp0.X::X(xx);//改写函数调用操作foo(__temp0);

foo()声明也因此要改变,像这样:

void foo(X& x0);

返回值初始化

X bar(){    X xx;    // 处理xx...    return xx;}

双阶段转化:

1.首先加上一个额外参数,类型是class object的一个reference。用来放置被“拷贝建构”得到的返回值

2.在return指令前安插一个copy constructor调用操作,以便将欲传回之object的内容当做上述新增参数的初值。

产生C++伪代码如下:

void bar(X& __result){     X xx;    xx.X::X();    //...处理xx   //编译器产生copy constructor调用操作   __result.X::X(xx);  return;}

使用者层面做优化

X bar(const T&y,const T& z){     return X(y,z);}

去除了不必要的copy constructor处理

编译层面做优化

提到一个重要的概念:NRV(Named Return Value)优化

该优化被视为标准C++编译器一个义不容辞的优化操作

X bar(){    X xx;   //...处理xx   return xx;}编译器把其中xx以__result取代:void bar(X& __result){    __result.X::X();   //...直接处理__result   return;}

如果不适用NRV优化策略,则会多调用一次构造和析构函数(X xx)以及拷贝构造函数

同时NRV也带来了一些问题:

1.优化是由编译器默默完成,是否真的完成,并不十分清楚。

2.一旦函数变得比较复杂,优化变得难以施行。比如函数中含有嵌套,cfront就会静静关闭优化。

3.某些程序员真的不喜欢应用程序被优化。

拷贝构造函数要注意,如果有虚函数,不要使用memset方式,因为会修改vptr,导致不正确。

第2.4节 成员们的初始化队伍

出现下述情况,应该使用member initialization list:

1.当初始化一个reference member

2.当初始化一个const member

3.当调用一个base class的constructor,而它拥有一组参数

4.当调用一个member class的constructor,而它拥有一组参数

class Word{    string _name;    int _cnt;public:    //没有错误,但是效率低    Word()    {        _name=0;        _cnt=0;    }};//constructor可能扩张结果Word::Word(){    //调用string的default constructor    _name.string::string();    //产生暂时性对象    string temp=string(0);    //"memberwise"的拷贝_name    _name.string::operator=(temp);    //摧毁临时对象    temp.string::~string();    _cnt=0;}

list中的项目次序是由class中的member声明次序决定,而不是initialization list中的排列次序决定。

一个忠告:使用“存在于constructor体内的一个member”,而不要使用“存在于member initialization list中的member”,来为另一个member设定初值。

//调用FooBar::fval()可以吗class FooBar:public X{    int _fval;public:    int fval(){
return _fval;}function FooBar(int val):_fval(val),X(fval())};//可能扩张结果FooBar::FooBar(){ //不是一个好主意 X::X(this,this->fval()); _fval=val;}

转载于:https://www.cnblogs.com/ChengDongSheng/archive/2012/06/05/2537255.html

你可能感兴趣的文章
01: socket模块
查看>>
mysql触发器
查看>>
淌淌淌
查看>>
web页面实现指定区域打印功能
查看>>
win10每次开机都显示“你的硬件设置已更改,请重启电脑……”的解决办法
查看>>
C++有关 const & 内敛 & 友元&静态成员那些事
查看>>
函数积累
查看>>
bzoj 4815 [Cqoi2017]小Q的表格——反演+分块
查看>>
Swift 入门之简单语法(六)
查看>>
shim和polyfill有什么区别
查看>>
Failed to load the JNI shared library “E:/2000/Java/JDK6/bin/..jre/bin/client/jvm.dll
查看>>
〖Python〗-- IO多路复用
查看>>
栈(括号匹配)
查看>>
Java学习 · 初识 面向对象深入一
查看>>
源代码如何管理
查看>>
vue怎么将一个组件引入另一个组件?
查看>>
bzoj1040: [ZJOI2008]骑士
查看>>
LeetCode 74. Search a 2D Matrix(搜索二维矩阵)
查看>>
利用SignalR来同步更新Winfrom
查看>>
反射机制
查看>>