首页 > 其他 > 详细

对象模型 ------- Constructor 构造函数

时间:2017-01-20 10:03:44      阅读:264      评论:0      收藏:0      [点我收藏+]

前言:

        之前,在编写C++的一些代码的时候,在类的构造、析构、赋值函数部分,偶尔会出现编译器做出和我初想不一样的决策。后来,看了<深度探究C++对象模型 >这本书后,有的疑问或多或少得到了解决。所以在此,想写一些笔记来记录这些疑问的地方,可能后面关于对象模型还会关于其他方面的文章。不过这个系列文章都主要是以探讨为核心,以后在编程中遇到的相关问题也会不定期在这系列文章继续补充。有的疑问的解答可能只是经过本人的探究得到的猜测,如果有错误,还望指出,谢谢!



引言:

在看这篇文章之前,先看如下几个问题:


        1.任何class如果没有用户定义的default constructor或者copy constructor,编译器就会合成一个出来?

        2.编译器合成出来的default constructor会显式设定 “class 内每一个datamember” 的默认值?

        3.编译器合成出来的copy constructor的拷贝是memberwise copy 还是 bitwise copy?

又或者是如下几段代码:
//Test类,下面的样例代码都会使用该类。
#include <iostream>
using namespace std;
class Test
{
public:
    Test() { cout << "默认" << endl; }
    Test(int a) { cout << "带参" << endl; }
    ~Test() { cout << "析构" << endl; }
    Test(const Test&) { cout << "拷贝构造" << endl; }
    Test& operator=(const Test&) {
        cout << "赋值" << endl;
        return *this;
    }
};
        1.如下两句代码分别会构造几个object?
int main()
{
    Test test1 = 1;           //第一句
    Test test2 = Test(1);     //第二句
    return 0;
}
        2.如下代码中fun1和fun2的区别?
Test fun1(){
    Test a(1);
    return a;
}
Test fun2(){
    return Test(1);
}
int main(){
    { Test test1 = fun1(); }
    cout << "--------------分隔符---------------" << endl;
    { Test test2 = fun2(); }
    return 0;
}
        3.如下代码两行代码的区别?
int main(){
    Test test1;
    Test test2();
    return 0;
}
        4.如下代码两行代码的区别?
Test fun(){
    return Test();
}
int main(){
    Test test1(fun());
    Test test2(Test());
    return 0;
}
        在看本篇文章之前,你可以尝试在编译器中编译运行以上代码,看看编译器对以上代码做出的解释、决策跟你所预期的是否一样。如果你对以上问题留有困惑,那么你的问题应该在本篇文章的后续内容都能得到解答。


一 . Constructor (Default or Copy)的合成


        首先,我先要对第一个问题进行探讨:
                “任何class如果没有用户定义的default constructor或者copy constructor,编译器就会合成一个出来?”
        在之前对C++的学习中,可能有些朋友(包括本人)会有以上这个概念的认识。然而事实上,这个结论是错误的。
        要真正地理解编译器会在何时自主合成一个Constructor,我们首先需要了解Constructor的相关功能:

         1. 初始化member
         2. 初始化base obj
         3. 初始化vptr(如果存在)
         4. 初始化virtual base obj相关信息(如果存在)
        什么时候编译器会自主合成Constructor呢?我们就要分清楚什么是程序需要,什么是编译器需要。
        很显然,对于编译器需要的,一定是被合成出来的constructor函数要担负起编译器需要执行的相关功能。
        在<深度探究C++对象模型 >一书中说到,对于以下四种情况,编译器会默认合成contructor:

         1. 带有 Constructor 的 Member Class Object (无论是用户提供还是编译器合成的),该类对应合成相应的Constructor。
         2. 带有 Constructor 的 Base Class (无论是用户提供还是编译器合成的),该类对应合成相应的Constructor。
         3. 带有一个Virtual Function (包括父类中存在 Virtual Function )
         4. 带有一个Virtual Base Class ( 包括父类中存在Virtual Base Class)
        对于1.如果一个类不含有用户定义的 Default Constructor 且不含合成的 Constructor 函数,那么对其 member 是不进行初始化的,而如果member中含有其他 class object 递归同样操作。如果存在1所属情况,那么就必须由编译器合成一个默认构造函数来调用其 member class object 的 constructor。那么 copy constructor 同理。

        对于2.其实和上一个的原因差不多,都是因为需要调用base object 的 constructor ,所以编译器合成了对应的 constructor 函数 。

        对于3.在这里不过多着墨于vptr(虚表指针)的介绍,如果对此不清楚的可以在这里先理解为一个拥有虚函数class 所特有的指针。而这个指针需要在构造对象之初就要完成对该指针的初始化,所以编译器合成了 default and copy constructor 。

        对于4.虚基类,我们知道,对于虚基类,从多个途径派生出来的虚基类在子类有且只有一份,那么在其派生链中间类对于虚基类需要固定其实际偏移量。这句话可能不容易理解,嗯,你可以这样想,对于一个对象取其成员,其实实际上是获取该成员的偏移量,再根据自身的地址得到,然而如果对于一个中间类A,其reference或者pointer可能代表的是其任何子类。既然A*或者A&可以代表的实际类型有很多可能,如果使得在这些类中对于虚基类拥有同一偏移量呢?所以,在这里,引入了跟vptr一样的实现方法,用指针指向虚基类。那么所以编译器需要合成 default and copy constructor 用于初始化该指针。

        其实以上只是对原因的简单阐述,如果有想深入了解编译器的小动作,可以自行阅读<深度探究C++对象模型 >一书 第二章。
        同理我们可以知道,对于问题二:
                 “编译器合成出来的default constructor会显式设定 “class 内每一个datamember” 的默认值?”
         答案肯定是No,编译器合成出来的default constructor只会对上述4中情况中所涉及到的内容进行初始化。

        既然以上的问题解决了,那么我们试图在这个问题的基础上继续衍生出新的问题:

                如果在需要合成constructor的情况下,用户提供了相应的constructor呢?编译器会做出如何的行为呢?

        既然我们知道,在上述四种情况中,编译器需要constructor函数来对其某些属性进行必不可少的初始化。所以当用户提供了constructor之后,编译器会在user code之前插入这些初始化代码 ,所以在如下代码中:

class A
{
public:
    A() {
        num = 0;
    }
    string str;
    int num;
};

        实际上编译器加入了如下代码:

//c++伪码
class A
{
public:
    A() {
        str.string::string();  //对member class object constructor的调用
        num = 0;
    }
    string str;
    int num;
};

        所以如果要对str进行非默认构造,请使用构造列表,否则如果在构造函数内部初始化就出现如下情况:

//c++伪码
class A
{
public:
    A() {
         //这里实际上是先使用默认构造函数构造一个str,然后在使用"abc"构造一个临时string对象,然后调用拷贝构造给str。
        str = "abc"; 
        num = 0;
    }
    string str;
    int num;
};


二 . Memberwise Copy and Bitwise Copy


        接着第三个问题:
                “编译器合成出来的copy constructor的拷贝是memberwise copy 还是 bitwise copy?”

        我们先来了解一下什么是Memberwise Copy , Bitwise Copy。

         1. Bitwise Copy(位逐次拷贝):对于一个对象在其所在内存按照一位一位的依次拷贝。特点:速度块。
         2. Memberwise Copy(成员逐次拷贝):对一个对象所有成员进行拷贝(拷贝方式要视成员和copy constructor实现而定),特点:控制力强。

        了解了两个copy方式的区别之后,我们再来回顾一下,copy constructor 合成出现的原因。
                1.需要对member class object 的 copy constructor进行调用。
                2.需要对base class 的 copy constructor 进行调用。
                3.需要对vptr进行初始化。
                4.需要对指向 virtual base class 的指针进行初始化。
        我们可以看出来,对于1,2,我们肯定不能把合成的copy constructor 视位Bitwise Copy。对于vptr呢?我们知道,派生类对象可以赋值给基类对象,虽然这是会发生sliced(截断),但是从语法和用法上是可取的。如果在这个情况下,合成的copy constructor按照Bitwise Copy。那么很明显,派生类的vptr被赋值给了基类的vptr。一旦使用该对象的指针进行对虚函数的调用就会Boom!!!!。同理,对于第4点也一一样。

        所以我们知道对于编译器合成的copy constructor 采用的是Memberwise Copy。这里值得注意的是:对于基本类型:int,char,double..等等还是使用的Bitwise Copy。而其他member class object 要参照其类内部copy constructor实现而定。

对象模型 ------- Constructor 构造函数

原文:http://blog.csdn.net/y1196645376/article/details/54619923

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!