2 构造、析构、赋值运算

来源:互联网 发布:淘宝有正品阿迪达斯吗 编辑:程序博客网 时间:2024/06/01 08:00
2017年8月11日 08:59:53



5 了解C++默认编写并调用哪些函数
    空类,默认生成:默认的无参构造函数、copy构造函数、析构函数、赋值运算符
    即:
        class Empty { };    / sizeof(Empty) == 1;    空类的大小为1,因为已经声明,需要分配实际的地址,不然无法调用。
        等价于:            / 编译器默认生成为 1,编译器不同,大小也可能不一样
        class Empty
        {
            Empty();
            ~Empty();
            Empty(const Empty &);
            Empty& operator=(const Empty&);
        };
    编译器默认生成的析构函数,为非虚,除非类本身继承了 带有虚析构函数的 父类

    默认的copy和赋值,都是浅拷贝,单纯地将来源对象的每一个 non-static 成员变量拷贝到目标对象。

    当声明了一个构造函数时,编译器不再生成默认的构造函数,但copy,赋值,还是会创建。

    如果类中有reference成员(引用,或者const指针),并且要支持 赋值操作,必须自己定义赋值运算符。
        一方面:深拷贝和浅拷贝;
        另一方面:引用不能指向不同类型。更改const也不合法,所以编译器不知道在默认的赋值运算符中,如何赋值。

    如果基类的 = 为private,那么编译器将拒绝为 派生类生成 = ,因为编译器默认的 = ,可以调用基类的 = 来处理基类的成员函数,
    但此时基类的为私有,无法调用。


6 如果不想使用编译器默认生成的函数,应该明确拒绝 

    一般来说,将成员函数声明为private而且故意不实现它们。
    原因: * private 可以阻止编译器默认生成(编译器默认生成的都是public)
           * 如果实现了,那么friend 和 member 可以调用。
                (不实现的话,链接器会发生抱怨)。

    可以声明一个基类,将需要阻止的成员函数,声明为私有,自定义的函数继承这个基类。
    这样自定义函数,也无法使用 对应的 私有成员函数。
    比如: 基类的copy构造是私有,那么派生类,就不能用copy构造函数,不然编译器会报错。


7 为多态基类声明为 virtual析构函数
    当类中存在virtual函数,说明该类试图做一个基类,此时必须有virtual析构函数,不然在发生多态时,派生类可能无法析构,造成内存泄漏。
    如果类中不存在virtual,说明这个类并不想做一个基类,此时不应该有virtual析构函数。
    因此:只有当class内至少有一个virtual函数,才为它声明virtual析构函数。(因为这种标准只适合用在多态性质上)

    标准的string,STL,都不带虚析构函数

    为抽象class声明一个 纯虚析构函数。这样既定义了抽象类,又保证了其他函数不用成为 pure virtual


8 别让异常逃离析构函数
    * 析构函数不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们或者程序结束。
        (在析构函数内 try 之后,就 catch)
    * 如果客户需要对某个操作函数运行器件抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。


9 绝不要在构造和析构过程中调用virtual函数
    因为在构造器件,virtual函数不是真的virtual函数,不会下降到派生类那一层,而是调用基类的virtual函数。
    派生类对象的基类构造期间,对象的类型是基类,(即使用RTTI检测,也是),virtual函数会被编译器解析至base class
    例如:
        class A                   
        {
        public :
            A()
            {
                fun();
            }

            virtual void fun();
        };

        class B : public A
        {
        public :
            virtual void fun();
        };

        B b;    这句话,将会调用A::fun(),而不是 B 中的

    同样的道理,也运用在析构函数之中。

    注意:
        有时候,构造函数,调用非虚函数,但是该非虚函数,会调用虚函数,此时比较隐蔽。

    可以使用辅助函数创建一个值传给base class 构造函数。(向上传值)
    如果此函数为static,就不可能意外指向 在派生类构造初期,没有初始化的成员变量。

10 令 operator= 返回一个reference to * this
    这是一个协议,并无强制性。

    A& operator=(const A& a)
    {
        ...
        return *this;
    }


11 在 operator= 中处理"自我赋值"
    例如:
        class Bitmap {...};
        class Widget
        {
            ...
        private :
            Bitmap *pb;
        };

    方案一:
        Widget& operator=(const Widget& rhs)
        {
            delete pb;
            pb = new Bitmap(*rhs.pb);
            return *this;
        }   
        该方案错误,当自我赋值时,会把rhs.pb先删掉

    方案二:
        Widget& operator=(const Widget& rhs)
        {
            if(this == &rhs)
                return *this;

            delete pb;
            pb = new Bitmap(*rhs.pb);
            return *this;
        }   
        当new失败了,pb会指向一个被删除的空间,异常不安全

    方案三:
        Widget& operator=(const Widget& rhs)
        {
            Bitmap *temp = pb;
            pb = new Bitmap(*rhs.pb);
            delete temp;
            return *this;
        }   
        new 抛出异常,那么pb还会指向原来的空间,并没delete,保持不变

    一种代替的方法:
        使用 copy and swap 技术,来保证赋值的同时,又保证异常安全
        Widget& operator=(const Widget& rhs)
        {
            Widget temple(rhs);
            swap(temple);    //自定义的swap
            return *this;
        }   


12 复制对象时勿忘其每一个成份

    在发生组合时:
    class A { .. };
    class B
    {
    public :
        B(const B & b)
        {
            ....    此时不要忘记初始化a,不然会调用a的默认构造函数
        }
    private:
        A a;
    };


    在发生继承时:
    class A { .. };
    class B : public A
    {
    public :
        B(const B & b) : A(b)    不要忘记调用基类的copy构造函数,不然会调用基类的默认构造函数
        {
            ....   
        }

        B& operator=(const B& b)
        {
            ...
            A::operator=(b);    调用基类的赋值操作,来赋值基类的成员变量
            ...
        }
    };

    如果copy构造函数 和 赋值运算符,有很多重复的动作。
    注意:不可以相互调用,来节省代码。
          而应该将这些重复动作,定义成一个共用的函数,copy构造 和 赋值运算符,都来调用。

    原因:
        * 赋值运算符 调用 copy构造,那么会试图建立一个已经存在的对象
        * copy构造 调用 赋值运算符,毫无意义,因为对象还没构造完成,哪来的赋值运算符呢?

原创粉丝点击