zoukankan      html  css  js  c++  java
  • 【转载】C++基本功和 Design Pattern系列 ctor & dtor

    最近实在是太忙了,无工夫写呀。只能慢慢来了。呵呵,今天Aear讲的是class.ctor 也就是constructor, 和  class.dtor, destructor. 相信大家都知道constructor 和 destructor是做什么用的,基本功能我就不废话了。下面先说效率的问题,让我们看个简单的例子:

    class SomeClass;   // forward declaration

    class AnotherClass {
    private:
        SomeClass SomeClassInstance;
    public:
        AnotherClass(const SomeClass & Para) { SomeClassInstance = Para; };
        ~AnotherClass();
    };

    也许这是很多初学者经常写出来的代码,Aear以前也写过。让我们来看看这段代码有什么问题。

    首先需要说明的是,在一个class实例化之前,所有的member都会被初始化,如果member是个class,那么那个class的 constructor就会被调用。也就是说,在运行AnotherClass的constructor之前,SomeClass的 constructor就已经运行了。接下来的代码里,SomeClassInstance又被重新执行了次 = 操作。也就是说,我们在给 SomeClassInstance附初值的时候,调用了2次SomeClass的method. 这个浪费也太大了,比较标准的方式是使用初始化列表,如下:

        AnotherClass (const SomeClass & Para): SomeClassInstance(Para) {};
    如果有多个类成员,可以用","来分割,如:

        AnotherClass (const SomeClass & Para1, UINT32 Para2):
                       SomeClassInstance(Para1),
                       SecondAttr(Para2),
                       ThirdAttr(Para3) {};
    值得注意的是, 类成员的初始化顺序和在类中的声明顺序应该一致。这个是有compiler来控制的,并不根据你在AnotherClass的 constructor中提供的初始化顺序来进行。所以,如果你想先初始化ThirdAttr,然后把ThirdAttr传到SecondAttr作为初始化参数,是会失败的。只有改变声明顺序才会成功。

    同理,在声明类变量被附初值的时候,使用拷贝构造函数,效率更高:

    =====错误=====
    class x1;
    x1 = x2;

    =====正确=====
    class x1(x2);

    ===================分割线===================

    从上面的例子可以看到,几乎所有的class,都需要提供拷贝构造函数,也就是 className(const className &)。同时值得注意的是,如果提供了拷贝构造函数,一般也就需要提供 "="操作,也就是 className & operator =  (const className &),说到 operator =, 也有必要强调下implicit type conversion的问题,这将会在以后的章节张有详细描述。至于为什么要提供 operator =,举个简单的例子:
     
    class1 {
    public:
        class1() { p = new int[100]; };
        ~class1() { delete[] p; };
    private:
        char* p;
    } x1, x2;

    如果class1不提供operator =, 那么运行 x1 = x2的时候,C++会运行最基本的拷贝操作,也就是 x1.p = x2.p,那么在 x1被释放的时候,delete p;被执行。这时候 x2再要访问p,p已经变成非法指针了。 也许有人会说,我才不会用x1 = x2这么危险的操作,那让我们看看更加隐性的操作吧,例子如下:

    void func(class1 Para) {...};

    func(x1);

    这时候,c++会调用class1的拷贝构造函数,来把参数从x1里拷贝到Para,如果class1没有提供copy constructor,那么c+ +就执行简单拷贝工作,也就是 Para.p = x1。当func返回的时候,Para被释放,调用 Para.~class1(),并且 delete p;那么x1.p就变成非法指针了。

    这样大家就知道为什么要同时提供copy constructor和 operator =了吧。特别是在class里有指针的情况下,必须提供以上2个method。如果不想提供,可以把他们设为private,代码如下:

    class1 {
    ...
    private:
        class1 (const class1 &);
        class1 & operator = (const class1 &);
    }
    这样别人在执行 = 和 func()的时候就会报错了。

    还有,在声明构造函数的时候,单参数的构造函数,最好都用explicit来声明,例如:

    class1 {
    public:
        class1(int Para) {...}
        ...
    };

    其中class1(int Para)是个单参数的构造函数,如果执行下列操作,如:

    class1 x1 = 2;

    的时候,因为2不是class1,所以c++会用隐性的类型转换,也就是把2转换成class1,因此会调用class1(2),然后用operator  = 符值给 x1. 这种操作经常会产生很多问题。比如如果我们提供了 operator == ,那么 在 if(x1 == 2)的时候,c++也会进行类似的操作,可能会产生我们不需要的结果。所以,对于这种单参数的constructor 最好做如下声明:

    explicit class1 (int Para) {...}

    这样做再执行 class1 x1 = 2;的时候就会报错了,explicit的意思就是C++ 的compiler不能做隐性类型转换,必须由程序员做type cast,比如:

    class1 x1 = static_cast<class1>(2) 才会成功。

    ===================分割线===================
    在运行constructor的时候,值得注意的一点就是,如果在constructor里,要初始化会throw exception的代码,一定要在constructor里catch。比如:

    class1 {
        class1()
        {
           pInt = new int[100];
           try {
               pClass2 = new pClass2;
           }catch(...)
           { delete pInt; throw; };
         }
    }

    大家看的明白了吧,如果不catch pClass2的exception,pInt分配的内存就不会释放,因为constructor如果失败,c++是不会调用destructor的。

    ===================分割线===================
    最后关于destructor,需要注意的是,如果是被继承的base class,destructor一定要是virtual。比如:

    BaseClass ()
    {
    public:
        BaseClass();
        virtual ~BaseClass();
    }

    DerivedClass : public BaseClass()
    {
    public:
        DerivedClass();
        ~DerivedClass();
    }

    BaseClass * pBase = static_cast<BaseClass *>(new DerivedClass());
    delete pBase;

    如果BaseClass的destructor是virtual,那么正确的ctor dtor调用顺序是:

    BaseClass();
    DerivedClass();
    ~DerivedClass();
    ~BaseClass();

    如果不是Virtual,调用顺序是:

    BaseClass();
    DerivedClass();
    ~BaseClass();

    也就是说,DerivedClass的派生类不能被正确调用,这主要是因为在delete的时候c++并不知道你delete的是  DerivedClass, 因此需要把BaseClass的 dtor 设置成 virtual, 这样可以使用 vptr在 vtbl中查找  destructor,从而能够正确的调用destructor。

    ===================分割线===================
    从上面的例子大家也看出来了,如果是派生类,那么就要调用基类的constructor,在多层次的派生类创建过程中,所以基类的constructor都要被调用。 destructor同理。因此要想提高效率,可以在关键代码短使用非派生类。

    也许有人会说,所有的constructor和destructor都被compiler inline了,但是即使是inline并且 base class的constructor中不进行任何操作,c++也要为每个类设置vptr,也是有不需要的overhead。当然,我们得到效率的同时,失去的是可扩展性,良好的程序层次结构等等,大家要根据具体情况来权衡。

  • 相关阅读:
    怎么查看京东店铺的品牌ID
    PPT编辑的时候很卡,放映的时候不卡,咋回事?
    codevs 1702素数判定2
    codevs 2530大质数
    codevs 1488GangGang的烦恼
    codevs 2851 菜菜买气球
    hdu 5653 Bomber Man wants to bomb an Array
    poj 3661 Running
    poj 1651 Multiplication Puzzle
    hdu 2476 String Painter
  • 原文地址:https://www.cnblogs.com/yangang92/p/5509933.html
Copyright © 2011-2022 走看看