构造函数语义学
--了解编译器在构造对象时背着我们干了什么勾当
Default Ctor 在需要的时候被构建出来~
什么需要? 是编译器需要而不是程序的需要,所以不要期望编译器生成的Ctor会帮我们把我们的成员变量初始化为零。那是程序的需要,也就是我们程序员的任务····
例如:
class Foo(public:int val;);
void foo_bar(){Foo bar; if(bar.val)dosth;} 不要期望编译器帮我们初始化它为0。只有global变量会初始化为0,初始化val这样的变量需要我们自己写代码的~~
Default Ctor分为trival(没用的)和non-trival的,下面讨论什么时候编译器需要ctor 也就是有用的ctor,这时候如果我们没有提供一个默认的default ctor 它会帮我们合成一个的~~
1.带有Default Ctor 的member class object
很好理解,既然内部成员含有default ctor 那么我们创建新的对象时需要调用它,而我们并木有调用它的函数,编译器自然会帮我们提供一个。如果我们提供了default ctor ,那么编译器会在我们提供的函数加入调用必须调用的member class 的default ctor。同样的在每一个ctor里面都将在用户写的代码之前调用需要调用的default ctor -------看例子:
class Dopey();class Sneezy{public:Sneezy(int val);Sneezy();};class Bashful{public:BashFul();};
class Snow_white{public: Dopey dopey;Sneezy sneezy;Bashful bashful;private:int muble};
如果Snow_white没有定义default ctor 那么编译器会创建一个,并在其中依照声明顺序依次调用dopey sneezy bashful的default ctor 然而如果:
Snow_white::Snow_white():sneezy(1024){muble=2045;}
编译器会扩张为
Snow_white::Snow_white():sneezy(1024){
dopey.Dopey::Dopey();
sneezy.Sneezy::Sneezy(1024);
bashful.Bashful::Bashful();
///////// explicit user code
muble = 2048;
}
2.派生自带有Default ctor 的 base class
同样道理如果父类有Default ctor 子类当然要调用,编译器会为想上面一样为我们加上
3.含有virtual functions的Class
创建object 时候需要ctor 来设置好vptr
4.带有virtual base class
virtual base 实现方法各有不同,然而共同点是必须是virtual base class 在其每一个derived class中的位置能够与执行期准备妥当
X A B C 菱形继承
void foo(const A*pa){pa->i=1024;}
foo(new A);foo(new C);
知道pa 后 i在对象中的位置并不是固定的,而是在运行时真正确定pa指向什么对象A还是C才能确定的,因此需要设定一个指向基类subobject的指针,所以需要ctor工作了
OK:
1.任何class 如果没有定义Default ctor 就会被合成一个
2.合成出来的Default ctor 会明确设定每一个data member值
错的很easy了~
-------------------------------------------------------------------------------
再来看 Copy Ctor:
copy ctor 负责用另一个对象初始化一个对象
operator = 负责用另一个对象给一个对象赋值
直接赋值时,传参时,返回时可能调用Copy ctor
Default member initialization~~~
也就是memberwise 的initialization
他会把每一个data member (内建的或者派生的)从一个object 拷贝到另一个object中去
如果object允许bitwise的拷贝那么编译器就不用生成一个nontrival的default copy ctor
什么时候不可以呢~
1 内含一个member object 而后者含有copy constructor (声明或者合成的)
2 继承一个base class 后者有copy ctor
3 含有virtual func
4 派生自一个继承链,其中有virtual base class
1和2 中编译器会把member 或者baseclass copy ctor 调用安插在合成的copy ctor 中
3 中:
如果两个同样类型的object赋值时,没有问题因为他们的vptr相同
但是考虑子类赋值给父类,此时vptr需要更改,那么此时不具有bitwise特性,因此需要编译器来加入语句正确更新vptr
4中:
每个编译器都承诺必须让derived class 中的virtual base class object 在执行期间准备妥当,维护位置完整性是编译器的责任。bitwise copy 有可能会破坏这个位置所以编译器需要在自己合成的copy ctor 中作出仲裁
同样问题发生在继承体系中子类向父类赋值时,由于对象模型问题,直接bitwise复制可能会导致base class object 的破坏(后面章节会有讨论)
--------------------------------------------------------------------------------
程序转化语义学:
X x0;
void foo(){X x1(x0); X x2=x0; X x3=X(x0);}
转化:重写定义,初始化操作会被剥除 copy constructor 调用会被安插
void foo(){ X x1;X x2; X x3; x1.X::X(x0); x2.X::X(x0); x3.X::X(x0);}
参数的初始化:
一种策略导入暂时的object
void foo(X x0);X xx; foo(xx);
转化:
X _tmp;
_tmp.X::X(x0); foo(_tmp);
foo变成 void foo(X& x0);
另一种是拷贝构建:
把实际参数直接建构造应该在的位置上,函数返回时局部对象的destructor 会执行
也就是说把x0建构在foo 的函数执行的地方
返回值的初始化:
X bar(){
X xx;
return xx;
}
返回值如何从局部对象xx拷贝而来呢?
一种方法:1.加上额外参数,类型是class object的reference,这个参数用来放置被拷贝建构的返回值 (注意拷贝建构也就是说他被放在应该在的位置,也就是说不是局部变量了)
2.return 指令之前安插一个copy constructor 调用操作 ,以便将传回的object 内容当做上述参数的初值
so 上面的程序变成了:
void bar(X& _result){
X xx;
xx.X::X(); //编译器产生的default ctor 调用
_result.X::X(xx);//编译器产生的copy ctor 调用
return ;
}
现在编译器必须转换bar的调用操作 X xx=bar();转换成 X xx; bar(xx); // 注意不用copy ctor了直接操作在xx上了 如果编译器做了优化 这就是named return value 优化
而:
bar.memfunc();//bar()传回的X 调用成员函数
变成:
X _tmp; (bar(_tmp),_tmp).memfunc();
同样道理函数指针 X(*pf)(); 变成 void (*pf)(X&);
使用者的优化:
X bar(const T& y,const T& z){
X xx;
通过 y z 计算xx
return xx;
}
这种情况下要iuxx被memberwise拷贝到编译器产生的_result中,
如果定义 ctor来利用yz计算xx则:
X bar(const T& y,const T& z){
return X(y,z);
}
变成:
bar(X& result){
result . X::X(y,z);
return;
}
无需copy ctor了
编译器的优化:
如上所述bar中返回了具名数值 named value,因此编译器有可能自己优化,方法是以result取代named return value
bar(X& result){_result.X::X(); 直接处理result 并不处理变量xx然后复制给result 这样就优化了}
这个优化的激活需要class提供一个copy ctor~~~~~~
Copy ctor 要不要:
如果一个class 符合bitwise的要求那么此时member wise 的拷贝已经高效简洁 无需加入了
但是如果class需要大量的member wise 初始化操作,如用传值方式返回objects,如果是这样提供一个copy ctor 可以激活nRV named return value 优化,这就很合理了
成员们的初始化过程:
什么时候必须用初始化列表:
1.初始化一个reference时 2.初始化一个const member 3.调用父类ctor而且有参数时4调用member class ctor 有参数
其他情况呢:
class word{string name;int cnt;public: name=0;cnt=0;}
编译器可能这么做:
word::word{
name.String::string();调用string的default ctor
string tmp=string(0);
_name.string::operator=(tmp);
tmp.string::~string();
cnt=0;
}
显然name放到初始化列表会更有效率 ,会变成
name.String::String(0);
而cnt这种内建类型则没有关系,放不放到初始化列表没有效率上的差别
初始化列表究竟让编译器做了什么????
编译器会一个个操作list中的式子,以适当次序在ctor内安插初始化操作在任何explicit user code 之前。
注意的地方:
list中的次序是按照members声明次序决定而不是list 中的排列顺序决定。
例如:class x{int i;int j; X(int val):j(val),i(j)}
错了 i先声明则i首先赋予val 然后用未初始化的j赋给i。。。
可以这样X::X(int val):j(val){i=j;}
由于会安插在explicit code 之前 所以没问题 会变成 j=val; i=j;
可否用member functions 初始化成员??
答案是可以的,因为和objects相关的this指针已经构建妥当,只是要注意函数调用的member是否已经构建妥当了即可
------ - - - ---------- -- --疲惫的结束线- - - - - -- - -----
name return value TEST:
~~~~1 cl /od 不开优化
#include <iostream>
using namespace std;
class RVO
{
public:
RVO(){printf("I am in constructor\n");}
RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
~RVO(){printf ("I am in destructor\n");}
int mem_var;
};
RVO MyMethod (int i)
{
RVO rvo1;
rvo1.mem_var = i;
return (rvo1);
}
int main()
{
RVO rvo;rvo=MyMethod(5);
}
输出:
I am in constructor //rvo 创建
I am in constructor // rvo1创建
I am in copy constructor // rvo1赋值给hiddern
I am in destructor // rvo1解构
I am in destructor // hiddern解构
I am in destructor // rvo 解构
A MyMethod (A &_hiddenArg, B &var)
{
A retVal;
retVal.A::A(); // constructor for retVal
retVal.member = var.value + bar(var);
_hiddenArg.A::A(retVal); // the copy constructor for A
return;
retVal.A::~A(); // destructor for retVal
}
A MyMethod(A &_hiddenArg, B &var)
{
_hiddenArg.A::A();
_hiddenArg.member = var.value + bar(var);
Return
}
~~~~2 cl /o2 代码同上
output
I am in constructor //rvo创建
I am in constructor //hiddern 创建
I am in destructor //hiddern 解构
I am in destructor //rvo解构
我不明白的是hiddern 怎么传给rvo ,我猜可能是编译器按照bitwise的复制方式进行的,此时编译器并没有直接建构结果于rvo上 ,看看下面的试验:
注:明白了, 结果直接建构在hiddern,然后通过operator = 传给rvo 。没有copy ctor因为拷贝构造函数是负责初始化的,而operator = 才是用来赋值的.
经过代码证明是对的,如果重载赋值运算符 输出变成:
I am in constructor //rvo创建
I am in constructor //hiddern 创建
I am in operator = //赋值操作~~
I am in destructor //hiddern 解构
I am in destructor //rvo解构
~~~~3 cl /od
#include <iostream>
using namespace std;
class RVO
{
public:
RVO(){printf("I am in constructor\n");}
RVO (const RVO& c_RVO) {printf ("I am in copy constructor\n");}
~RVO(){printf ("I am in destructor\n");}
int mem_var;
};
RVO MyMethod (int i)
{
RVO rvo1;
rvo1.mem_var = i;
return (rvo1);
}
void abc(RVO& i){
}
int main()
{
RVO rvo=MyMethod(5); //此时定义和赋值放到了一个表达式子
return 0;
}
output:
I am in constructor // rvo1 创建 注意 跟上面的第一种情况下的hiddern一样 rvo并没有调用ctor
I am in copy constructor // rvo1 拷贝构造给rvo 此时没有hiddern了 直接构建rvo了
I am in destructor // rvo1 析构
I am in destructor // rvo1 解构
~~~~3 cl /o2 再来~~~~ NRV出马
I am in constructor // rvo构建了
I am in destructor // rvo析构了
此时 mymethod中的一切都直接反映在rvo身上
ok~~~~~4个代码完全一样构造析构拷贝函数个数由2-6不等~~~over~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~