zoukankan      html  css  js  c++  java
  • 第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;
    }
  • 相关阅读:
    【BZOJ】2100: [Usaco2010 Dec]Apple Delivery(spfa+优化)
    【BZOJ】2101: [Usaco2010 Dec]Treasure Chest 藏宝箱(dp)
    【BZOJ】3404: [Usaco2009 Open]Cow Digit Game又见数字游戏(博弈论)
    【BZOJ】3403: [Usaco2009 Open]Cow Line 直线上的牛(模拟)
    【BZOJ】3402: [Usaco2009 Open]Hide and Seek 捉迷藏(spfa)
    【BZOJ】3400: [Usaco2009 Mar]Cow Frisbee Team 奶牛沙盘队(dp)
    【BZOJ】3399: [Usaco2009 Mar]Sand Castle城堡(贪心)
    【BZOJ】3392: [Usaco2005 Feb]Part Acquisition 交易(spfa)
    【BZOJ】2020: [Usaco2010 Jan]Buying Feed, II (dp)
    【BZOJ】2015: [Usaco2010 Feb]Chocolate Giving(spfa)
  • 原文地址:https://www.cnblogs.com/ChengDongSheng/p/2537255.html
Copyright © 2011-2022 走看看