默认构造函数和拷贝构造函数总结

来源:互联网 发布:小智淘宝店外设店 编辑:程序博客网 时间:2024/06/06 17:47

default constructor的构造行为:
有四种情况,会造成编译器必须为未声明constructor的class合成一个default constructor。C++ Standard把那些合成物称为implicit nontrivial default constructors。被合成出来的constructor只能满足编译器(非程序)的需要。它之所以能够完成任务,是借着调用member object或base class的default constructor或是为每一个object初始化其virtual function机制或者virtual base class机制而完成的。至于没有存在那四种情况而又没有声明任何constructor的classes,我们说它们拥有的是implicit trivial default constructors,它们实际上并不会被合成出来。
四种情况具体是:
member class object存在default constructor的class;
派生自存在default constructor的class;
存在虚函数的class;
带有virtual base class的class;

也就是说,以下两个都是误解:    任何class如果没有定义default constructor,就会被合成出来一个;    编译器合成出来的default constructor会显示设定class内每一个data member的默认值。

注:
1.虽然以上情况会生成implicit nontrival default constructors,但是其实可以说和implicit trivial default constructors并没有什么区别,因为它并不会初始化其他的东西;
2.如果一个class存在其他constructors而不存在default constructor,那么default constructor就不会被合成出来,也就是没法直接根据default constructor生成对象,而初始化这些东西的代码会加到每一个constructor;
3.如果class既有member class object,又继承自有default constructor的class,那么它会先初始化base class,后初始化member class object。
4.测试可以发现,即使有default constructor,初始化这些东西(上面提到的类型的成员,基类)的代码也会加到每一个constructor中。

/*************************************************************************    > File Name: trivial_default_cosntructor.cpp    > Author: canispeakchinese    > Mail: canispeakchinese@qq.com    > Created Time: Mon 01 May 2017 11:13:51 AM CST ************************************************************************/#include <iostream>using namespace std;class Foo {public:    int val;    Foo *pnext;};class Bar {public:    Bar() : val(683945783) {}    Bar(int i) {}    int val;    Foo *pnext;};void foo_bar() {    Foo bar;//程序要求bar's members 都被清为0,但这不是编译器的需求,所以不会被清为0,即此时会有一个trivial default constructor,也就是什么都不做的default constructor。    cout << bar.val << " " << bar.pnext << endl;}int main() {    foo_bar();    //Bar bar;因为存在constructor,所以不会合成default constructor    Bar b(123);    Bar c;    cout << b.val << " " << c.val << endl;    return 0;}运行结果如下:6295032 0x400a6a2 683945783
/*************************************************************************    > File Name: nontrivial_default_constructor.cpp    > Author: canispeakchinese    > Mail: canispeakchinese@qq.com    > Created Time: Mon 01 May 2017 11:27:23 AM CST ************************************************************************/#include <iostream>class Foo {public:    Foo() { test = -11235; }    int test;};class Bar {//没有任何constructor,但是内含一个member object,而后者有default constructor,所以这个class的implicit default constructor就是"nontrivial",编译器需要为该class合成出一个default constructor,不过这个合成操作只有在constructor真正需要被调用时才会发生。           //这个default constructor内含必要的代码,能够调用class Foo的default constructor来处理member object Bar::foo,但是并不产生代码初始化Bar::str,因为初始化str是程序员的责任public:    Foo foo;    char *str;};class Bar4 {public:    Bar4(int i) {}    Foo foo;};class Bar2 {public:    Bar2() {str = 0;}//此时程序显式的定义了default constructor,满足了程序的需要,而编译器还需要初始化member object foo,而且又不能再定义default constructor    //此时编译器的动作是如果class A内含member class objects,那么class A的每一个constructor必须调用每一个member class的default constructor,编译器会扩张已存在的constructors,比如本例中会被扩张成:Bar::Bar() {foo.Foo::Foo(); str=0}    //如果有多个,那么编译器会按member声明顺序依次调用每一个member所关联的default constructors。这些代码会被安插在explicit user code之前。    Foo foo;    char *str;};class Bar3 : public Foo {    //没有任何constructors的class派生自一个带有default constructor的base class,那么这个dirved class的default constructor会被视为nontrivial,因此需要被合成出来。它将调用上一层base classes的default constructor(根据他们的声明顺序)。对于后继派生的class来说,这个合成的和被显示提供的没有区别。    //如果设计者提供多个constructors,但其中都没有default constructors,编译器会扩张现有的每一个constructors,将"用以调用所有必要之default constructors"的程序代码加进去。它不会合成一个新的default constructor。    //如果这个class正好又存在带有default constructor的member class object,那些default constructor也会被调用,在所有的base class constructor都被调用之后    Bar3(int i) {}};class Foo2 {public:    Foo2() {}    Foo2(int i) {}    Foo f;};class Foo3 {public:    Foo3(int i) {}    Foo f;};class X {public: int i;};class A : public virtual X {public: int j;};class B : public virtual X {public: double d;};class C : public A, public B {public: int k;};void foo(A* pa) {pa->i = 1024;}//正常情况下根本不知道A*指向的到底是什么类型,所以有一个解决方法是给derived class object中每一个virtual base classes安插一个指针来解决这个问题,这时候安插指针的工作自然是在default constructor中实现了//void foo(A* pa) {pa->vbcX->i = 1024}一种可能的编译器转化,显然vbcX就要在构造时被完成using namespace std;int main() {    //Bar3 a;所以这样就不行,因为Bar3有其他的构造函数,所以程序不会合成一个新的default constructor,即使是trivial default constructor(因为即使合成了也满足不了编译器的需要)。    //Bar4 a;同理这样也不行    Foo2 f(-1);    cout << f.f.test << endl;    Foo3 f2(-1);    cout << f2.f.test << endl;    return 0;}运行结果:-11235-11235

copy constructor的构造操作:
有三种情况会以一个object的内容作为另一个class object的初值:
X xx=x;//显示的以一个object的内容作为另一个class object的值
void foo(X x);//当object被当作参数交给某一个函数
X foo_bar();//当函数传回一个class object
user-defined copy constructor的实例:
X::X( const X& x);或者Y::Y(const Y& y, int z = 0);
大部分情况下,当一个class object以另一个同类实例作为初值,上述的constructor都会被调用。这可能会导致一个临时性class object的产生或导致程序代码的蜕变(或两者都有)(这句话待理解。。。。)
如果class没有提供一个explicit copy constructor,则编译器内部是以所谓的default memberwise initialization手法完成初始化目标object的,也就是把
每一个内建的或派生的data member的值,从某一个object拷贝到另一个object身上,不过它并不会拷贝其中的member class object,而是以递归的方式施行memberwise initialization(没大明白意思,总之gnu编译器中如果member class object本身有显式的拷贝构造函数,那么会执行其的拷贝构造函数来拷贝该member class object)。
注:深度探索C++对象模型书上说:“default constructors和copy constructors在必要的时候才由编译器产生出来。这个句子中的必要意指当class不展现bitwise copy semantics时”。“就像default constructor一样,C++ Standard上说,如果class没有声明一个copy constructor,就会有隐式的声明或隐式的定义出现。和以前一样,C++ Standard把copy constructor区分为trivial和nontrivial。只有nontrivial的实例caihibei合成于程序之中。决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的bitwise copy semantics(可以理解成直接进行内存拷贝吧)”
接下来可以说和前面所说的default constructor没有什么区别了,如果出现那四种情况,那么就会合成了一个copy constructor,但是和前面所说的一样,它只会在有拷贝构造函数的地方执行拷贝构造函数,在需要修改vitrual table指针的地方修改virtual table指针,该处理virtual base class subobject的时候处理一下,别的成员还是bitwise copy。
四种情况具体是:
member class object存在default constructor的class;
派生自存在default constructor的class;
存在虚函数的class;
带有virtual base class的class;

存在虚函数的class在default constructor和copy constructor时期分别要做什么:
对于存在virtual function的class,下面两个扩张行动会在编译器发生:
一个virtual function table(vtb1)会被编译器产生出来,内放class的virtual functions地址。
在每一个class object中,一个额外的pointer member会被编译器合成出来,内含相关之class vtb1的地址。
对于存在virtual base class的class来说:
virtual base class的实现法在不同的编译器中有极大的差异。然而每一种实现法的共同点在于必须使virtual base class在其每一个derived class object中的位置能够于执行期准备妥当。这样往往需要编译器在derived class的构造期间做一些事来确定virtual base class的位置。出现这个问题的来源在于多态,即可能一个A类型的指针指向的是B类型的对象,这时候必须要求A类型的指针能找到B类型的对象的virtual base class,其实细想一下,这个和virtual function的实现方式原理还是比较相似的。不理解文字的话可以看一下代码。
default cosntructor时期:
编译器必须为含virtual functions的object的vptr设定初值,编译器会安插一些代码来做这件事。
编译器必须为含virtual base class的object做一些事从而方便其找到virtual base class。
copy construct时期:
当以子类的对象来构造带虚函数的父类的时候,必须要保证父类的虚函数指针指向它自己的虚函数表,而不能是子类的虚函数表,所以这个时候编译器需要在构造函数中去做一些事情保证能找到正确的虚函数表。
当以子类的对象来构造带virtual base class的class的时候,必须要编译器显式的将virtual base class pointer/offset初始化,虽然不太理解,但是感觉和虚函数那地方意思差不多吧,而且这一块也不需要我们操心,即使我们有自己的构造函数,总不至于还要我们自己在构造函数里面写怎么初始化virtual base class pointer/offset吧。

总结:感觉其实了解这些东西都不是很有必要,我们只需要知道如果我们没有显式的写defualt constructor的话,那么基本数据类型成员变量就会不初始化,如果我们没有显式的写copy constructor,那么基本数据类型成员变量就会直接拷贝我们复制的object的对应变量的内存(而这样还是很危险的),至于virtual function和virtual base class什么的,和我们关系都不大,编译器会帮我们把这些弄好的。 
/*************************************************************************    > File Name: copy_constructor.cpp    > Author: canispeakchinese    > Mail: canispeakchinese@qq.com    > Created Time: Thu 04 May 2017 01:36:24 PM CST ************************************************************************/#include <iostream>using namespace std;class A {public:    int member;    A() {        member = 1;    }    A(const A&a) {        member = -1;    }};class B {public:    A a;};int main() {    B b;    B c = b;    B d(b);    cout << b.a.member << " " << c.a.member << " " << d.a.member << endl;//此时a成员不是通过memberwise initialization手法完成的,而是调用了A::A(const A&)拷贝构造函数    return 0;}运行结果:1 -1 -1
0 0
原创粉丝点击