构造函数语意学

Default构造函数操作:
首先需要说明:
带来的第一个问题,编译器什么时候合成默认构造函数(nontrivial)?答案是编译器需要的时候,而不是程序需要的时候。
1)class Foo { public: Foo(); ...};
class Bar { public: Foo _foo; char* _str...};
void foo_bar() {
Bar bar; 
}
此时编译器就会合成default 构造函数;类似
inline Bar::Bar() {
_foo.Foo:Foo();
}
编译器合成的默认构造函数只满足自己需要,不满足程序需要。
我们需要定义构造函数满足程序需要:类似
Bar::Bar() {
_str = 0;
}
编译器的行动:如果Class A内含一个或者一个以上的member class objects,那么每个constructor必须调用每一个member class的默认构造函数。(在已有的constructors之前安插一些代码)
// C++伪代码
Bar::Bar() {
_foo.Foo:Foo();  // 编译器插入
_str = 0;
}

2)同样的面对“带有Default Constructor”的base class的 时候也会采用类似上面的行为来对待构造函数。

3)“带有一个virtual Function”的class
对于virtual function编译期间编译器会做两个事情:
1. 产生virtual function table,其中放入class的virtual function的入口地址。
2. 每一个class object,会被编译器合成一个额外的vptr指向这个virtual table。
如果程序没有提供构造函数,编译器需要合成一个构造函数来添加代码做上面的两件事情。如果程序提供了一个以上的构造函数,编译器就会在构造函数user code之前插入一些代码来完成需要做的事情

4)带virtual Base Class的class
虚基类的引入我们都知道主要在于解决多重继承时,基类可能被多次继承,虚基类提供一个基类给派生类
class X { public: int i;};
class A : pulic virtual X {};
class B : pulic virtual X {};
class C : pulic A, public B {};
void foo(const A* pa) {
// 无法在编译期间决定 pa::X::i的位置
pa->i = 0;
}
编译器必须改变执行存取操作的这些代码,是X::i可以到执行期才决定下来。编译器可能类似策略:
void foo(const A* pa) {
// 其中_vbcX表示编译器所产生的指正指向virtual base class X
pa->_vbcX->i = 0;
}
然而_vbcX这类东西需要在object构造期间产生,所以对于class定义的每个constructor,编译器会安插一些代码来做“允许每个virtual base class 的执行期存期操作”代码,如果没有声明任何constuctors编译器必须合成一个default constructor。
以上是对于编译器合成default constructor的四种情况,容易的错误理解:
1)任何class如果没有定义default construtor就会合成一个
2) 合成的default constructor会显示的设定"class内每一个data member的默认值"
copy constructor的构造操作 (请先阅读,关于bitwise copy介绍请参看文章末尾)

默认拷贝构造函数(Default Copy Constructor)、默认赋值运算符(operator =)和默认析构函数,是C++类中的六大特殊成员函数中的三个。三者同时遵循一个原则:“一荣俱荣、一损俱损”。如果三者其中的任意一个被显示定义了(defined)那么三者必须都被显式定义。当果三者之一被程序员调用但未没有被显式声明时,编译器会隐含的实现这三个特殊成员函数。当用一个类对象去初始化另一个类对象时,需要用到拷贝构造函数;当用一个类对象去设定另一个类对象时,需要用到赋值运算符。

拷贝构造函数与赋值运算符都遵循“Default Memberwise Assignment&Initialization”原则,即对类中的每一个数据成员进行依次复制,但是通常编译器只采用bitwise copy方式复制(这样能够提高效率)。例如,对于只含有POD成员数据的简单类,bitwise copy方式绰绰有余。但是以下几种情况比较特殊:

1) 当class内含有一个member object时,并且后者的class中声明了一个copy constructor时;

依照“Default Memberwise Assignment&Initialization”原则,初始化member object时,需要编译器调用member class的拷贝构造函数,如果类中没有显式定义拷贝构造函数,就需要编译器构造,来调用成员类的拷贝构造函数。

2) 当类的基类中至少有一个含有拷贝构造函数时;

同样依照“Default Memberwise Assignment&Initialization”原则,需要依次构造所有的基类成员,如果没有显式定义默认拷贝构造函数,那么这部分工作就有编译器来完成。

3) 当类中声明一个或多个virtual functions时;

4) 当类的派生链中有一个或多个virtual base class时;

这里由于虚拟函数的机制,需要初始化vbtlvptr。这部分需要编译器来完成(本身虚拟机制就是从编译器角度来实现的)。

以上几种情况如果程序员未显式定义拷贝构造函数,编译器会自动完成拷贝构造函数的实现,用以满足编译器的需要,不过此时编译器合成的版本不在以bitwise的拷贝方式来完成。

以上四种情况都能容易理解,因为其进行bitwise copy的时候会发生错误,所以不展现bitwise copy semantics(需要特别注意的是1,2点中,需要base class和类成员变量所在class具有copy constructor)
关于第三种情况:
当编译器导入一个vptr到class之中的时候,class就不在展现bitwise semantics,编译器需要合成一个copy constructor将vptr适当的初始化。
例子:
class ZooAni {
virtual void draw();
...
}
class Bear: public ZooAni {
void draw();
...
}
Bear y;
Bear w = y;
此时w的vptr被设定为指向Bear class的virtual table。因此也就是所将y的vptr拷贝给w是vptr安全的.
ZooAni z = y;
此时z的vptr不能直接指向Bear的virtual table(也就是不能直接通过将y的vptr"bitwise copy"到z的vptr。
上述例子表达的是合成的copy constructor会显示设定object的vptr指向ZooAni的virtual table,而不是直接通过拷贝。

第四种情况:
virtual base class表示的必须让“derived class object中virtual base class subobject位置”在执行期准备妥当,然而bitwise copy可能破坏这个位置
class ZooAni {}
class Raccoon: virtual public ZooAni {}
class RedPanda: public Raccoon {}
RedPanda red;
Raccoon rac = red;  // 此时如果直接进行bitwise copy,virtual base class subobject位置发生破坏。
 
另外需要注意的是,合成copy constructor中,如int,指针等nonclass members也会被复杂。
例子:
class Word {
int cnt;
string str;
}
合成的copy constructor
inline Word ::Word( const Word& wd) {
str.string::string(wd.str);
cnt = wd.cnt;
}

从编译器编译连接角度,以上四种情况下如果未定义拷贝构造函数,编译器为了编译工作的顺利进行,会自定义拷贝构造函数;从编程者角度,如果类比较复杂(例如含有指针、引用等),单单依靠编译器定义默认拷贝构造函数,程序是无法达到预定效果的,所以此时往往需要程序员显式定义出自己的拷贝构造函数。


程序转化语意学:
1)返回值的初始化:
X bar() {
X xx;
return xx;
}
问题:返回值如何从局部对象xx中拷贝?
编译器的转化
void bar(X& __result) {
X xx;
xx.X::X();//默认构造函数操作
_result.X::X(xx) // copy constructor操作
return;
}

X xx = bar();编译器转化为
X xx;
bar(xx);

2) 编译器层面named return value(NRV)优化:(有兴趣可以在g++通过优化层次上开启NRV和不开启做下对比)
void bar(X& __result) {
_result.X::X() // 默认构造函数直接调用
return;
}
3)使用着层面的优化
X bar() {
X xx;
return xx;
}
写成  // 这种写法更多的是考虑了效率和不是优先抽象化
X bar() {
return X();
}
编译器的转化:
void bar(X& __result) {
_result.X::X() // 默认构造函数直接调用
return;
}

深浅拷贝基础知识:memberwise copy(深拷贝) 和 bitwise copy(浅拷贝)
memberwise copy: 在初始化一个对象期间,基类的构造函数被调用,成员变量被调用,如果它们有构造函数的时候,它们的构造函数被调用,这是一个递归的过程.
 bitwise copy:原内存拷贝, 例子,给定一个对象object,它的类型是class Base.对象object占用10字节的内存,地址从0x0到0x9.如果还有一个对象objectTwo,类型也是class Base.那么执行objectTwo( object);如果使用Bitwise拷贝语义,那么将会拷贝从0x0到0x9的数据到objectTwo的内存地址,.也就是说Bitwise是字节到字节的拷贝。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值