第2章 构造函数语意学

问题:对于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 semantics
class Word
{
public:
    Word(const char*);
    ~Word(){delete []str;}
    //...
private:
    int cnt;
    char *str;
};

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

但是class Word变换一下data member

//以下声明未展现出了bitwise copy semantics
class Word
{
public:
    Word(const string&);
    ~Word(){delete []str;}
    //...
private:
    int cnt;
    string str;
};

//string声明了一个explicit copyconstructor
class 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/p/2537255.html