C++构造函数简析

来源:互联网 发布:ubuntu 用户管理 编辑:程序博客网 时间:2024/05/18 03:42

所有编译器自动生成的函数都是public的,如类的默认构造函数

Efective C++条款05:了解C++默默编写并调用哪些函数

编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。

class Empty{

public:

Empty(){}     //default构造函数

Empty(const Empty& rhs){}  //copy构造函数

~Empty(){}   //析构函数

Empty& operator=(const Empty& rhs){} //copy assignment操作符

};

copy构造函数  

什么时候会调用copy构造函数??

在我们提供这样的代码:String test1(test2)时,它会被调用;当函数的参数列表为按值传递,也就是没有用引用和指针作为类型时,

如:void show_String(const String),它会被调用。其实,还有一些情况,但在这儿就不列举了。

StringgetBack(String s){}

在用类对象作为参数传入时和将类对象作为返回值返回时都会发生类的拷贝,调用类的拷贝构造函数来构造一个临时变量。

 

浅复制和深复制

如果一个类拥有资源(堆或者是其他系统资源),当这个类的对象发射复制过程时,资源重新分配,这个过程就是深拷贝,反之对象存在资源,但复制过程并未复制资源的情况称为浅拷贝。

在复制对象过程中,浅拷贝容易出现问题,在释放资源时会产生资源归属不清的情况,我们需要将拷贝过程转化为深拷贝。

Stringtest1("第一个范例。");

 

String*String1=new String(test1);

 

cout<<*String1<<endl;

 

delete String1;

 

cout<<test1<<endl;//在Dev-cpp上没有任何反应。

因为String1复制过程是浅拷贝,所以String1和test1共享同一块资源,在析构了string1后,该资源被释放,所以test1所指向的资源也不存在了,再继续访问就会出现错误。

编译器提供的默认copy构造函数只是复制了指针,而并没有复制指针指向的数据,所以需要自己定义copy构造函数。

copy assignment构造函数

平时,我们可以写这样的代码:x=y=z。(均为整型变量。)而在类对象中,我们同样要这样,因为这很方便。而对象A=B=C就是A.operator=(B.operator=(c))

而这个operator=函数的参数列表应该是:const String& a,所以,大家不难推出,要实现这样的功能,返回值也要是String&,这样才能实现ABC

String&String::operator=(const String& a)

 

{

 

delete [] str;//先删除自身的数据,以免内存泄漏

 

len=a.len;

 

str=newchar[len+1];

 

strcpy(str,a.str);//此三行为进行拷贝

 

return *this;//返回自身的引用

 

}

是不是这样就行了呢?我们假如写出了这种代码:A=A,那么大家看看,岂不是把A对象的数据给删除了吗?这样可谓引发一系列的错误。

所以,我们还要检查是否为自身赋值。只比较两对象的数据是不行了,因为两个对象的数据很有可能相同。我们应该比较地址。以下是完好的赋值函数:

String&String::operator=(const String& a)

 

{

 

if(this==&a)

 

return *this;

 

delete [] str;

 

len=a.len;

 

str=newchar[len+1];

 

strcpy(str,a.str);

 

return *this;

 

}

考虑到构造函数内部可能发生异常,要使String的实例保持有效的状态,我们需要实现异常安全性。

String&String::operator=(const string& a)

{

     if(this!=&a)

    {

        string strtemp(a);

char*pTemp=strtemp.str;

strtemp,str=str;

str=pTemp;

    }

}

 

条款6:若不想使用编译器自动生成的函数,就该明确拒绝---实现不可拷贝的类

为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。使用像

class Uncopyable{

protected:

    Uncopyable(){}

    ~Uncopyable(){}

private:

    Uncopyable(const Uncopyable&)

    Uncopyable& operator=(constUncopyable&)

};


这样的base class也是一种做法,但是如果在member函数或friend函数内有类的拷贝,以上的机制并不能阻止。那么可以由下面的继承基类来实现

class Nocopy{

public:

private:

    Nocopy(const Nocopy&)           //只有声明

    Nocopy& oprator=(const Nocopy&)

};

专门为了阻止copying动作而设计的base class为求阻止Nocopy对象被拷贝,我们可以继承Uncopyable:

class Nocopy:private Uncopyable{};

只要任何人,甚至是成员函数或是友元函数要拷贝类,当去构造基类时,由于基类的拷贝函数是private的,子类的成员函数或友元函数没有权限去访问基类的private成员,故编译器会报错。

在C++中设计实现一个不能被继承的类

1.单例模式

单例模式的类构造在堆上

2.简单的有将类的构造函数和析构函数声明为private,但和上面不可拷贝类的情况一样,无法阻止类中的member函数或友元类的继承,故采用友元+虚继承机制

class A

{

private:

    A(){}

    ~A(){}

 

    friend class B;

};

 

class B : virtualA

{

};

 

class C : B

{

};

 

利用友元类B可以在栈上构造A,系统构造C时会先跳过B先构造基类A的实例,但A的构造函数是private的,C并没有权限访问

虚拟继承?

C++如何阻止一个类被实例化

将构造函数或析构声明为private或将类声明为抽象类(共同特征的一种抽象)

Efective C++条款07:为多态基类声明virtual析构函数

C++多态在析构虚函数上的体现

将析构函数为声明为虚函数后,在析构时发生的不同

//A是一个父类 , 析构函数不是虚函数 

class A 

public: 

     A() 

    { 

        cout << " Aconstructor" << endl; 

    } 

      ~A() 

    { 

        cout << " A destructor"<< endl; 

    } 

}; 

 

//B是A的子类 

class B : publicA 

public: 

    B() 

    { 

        cout << " Bconstructor" << endl; 

    } 

    ~B() 

    { 

        cout << " B destructor"<< endl; 

    } 

}; 

 

//C是一个父类 , 析构函数是虚函数 

class C 

public: 

    C() 

    { 

        cout << " Cconstructor" << endl; 

    } 

    virtual ~C() 

    { 

        cout << " C destructor"<< endl; 

    } 

}; 

 

//D是C的子类 

class D : publicC 

public: 

 

    D() 

    { 

        cout << " Dconstructor" << endl; 

    } 

    ~D() 

    { 

        cout << " D destructor"<< endl; 

    } 

}; 

A *a=new B;

delete a;//输出A destructor

//A中析构函数不是虚函数,在将类型转换为A后,B中只留下了基类中的数据,所有析构时只需要析构A的部分即可

C* c=new D;//出处D destructor、Cdestructor

//C中析构函数为虚函数,在D中维护一个虚拟表后存放了虚函数--析构函数,所以不管D的类型如何变化,D中的虚函数表没变,所以在析构时会先调用D的析构函数,再调用C的析构函数析构C的部分。

条款09:绝不在构造和析构中调用虚函数

在构造和析构中,虚函数会被当做非虚函数处理,无法实现多态

条款10:令operator=返回*this的reference

重写赋值构造函数,外部可实现a=b=c的连续赋值

条款11:在operator=中处理自我赋值

在重写赋值构造函数中先进行自同判断
if(*this==a) return *this;
注意资源的释放顺序

条款12:复制对象时不要忘记每一部分

在重写赋值或拷贝构造函数时,不能忘记基类部分的复制

C++资源管理

条款13:以对象管理资源

为了确保资源最后总是会被释放,我们需要将资源放进对象(智能指针)内,当控制流离开范围,该对象的析构函数会自动释放那些资源。
由于auto_ptr被销毁时会自动删除它所指之物,所以一定要注意别让多个auto_ptr同时指向同一对象。为了预防这个问题,auto_ptr有个不寻常的特质,若通过copy构造函数和copy assignment操作符赋值它们,它们会变为null,复制所得的指针将取得资源的唯一使用权。
STL容器要求起元素发挥“正常的复制行为”,因此这些容器容不得auto_ptr。

auto_ptr的替代方案十“引用计数型智能指针”("reference-counting smart pointer", RCSP),它会持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源。但是RCSP无法打破环状引用,例如两个其实已经没有被使用的对象彼此互指,因此好像还处于被使用状态。

shared_ptr就是个RCSP。可应用在STL容器上以及其它auto_ptr不可用的情况

因为auto_ptr和shared_ptr两者在析构函数中做的是delete而非delete[]。那意味着在动态分配的数组上使用auto_ptr和shared_ptr十个馊主意。但这样做仍能通过编译,所以一定要注意这个情况。

可以使用boost::shared_array或者以vector代替数组来实现目的。


条款14:在资源管理类中小心copy行为

条款15:在资源管理类中提供对原始资源的访问

API直指资源本身,通过显式或隐式放入方式将管理类转化为资源本身

条款16:成对使用new和delete要采用相同形式

如果你调用new的时候使用了[],你必须在对应调用delete时也使用[]。如果你调用new的时候没有使用[],那么也不应该在delete的时候用。

在此,特别要注意typedef了的数组。

[cpp] view plain copy
  1. typedef std::string AddressLines[4];  
  2. std::string *pal = new AddressLines;  
  3. delete pal; // 行为未定义  
  4. delete [] pal; // 正确删除  
为了避免此类错误,应当尽量不要对数组形式作typedef操作。

条款17:以独立语句将new的对象放入智能指针中


虚拟继承

无法将虚拟继承的子类和父类使用强制转换,

假设derived继承自base类,那么derivedbase是一种“is a”的关系,即derived类是base类,而反之错误;

假设derived虚继承自base类,那么derivdbase是一种“has a”的关系,即derived类有一个指向base类的vptr

virtual base class的原始模型是在class object中为每一个有关联的virtual base class加上一个指针vptr,该指针指向virtual基类表。有的编译器是在继承类已存在的virtual table直接扩充导入一个virtual base class table。不管怎么样由于虚继承已完全破坏了继承体系,不能按照平常的继承体系来进行类型转换。

Effective C++条款08:别让异常逃离析构

如果在析构中不捕捉异常,析构结束,那么析构的工作未完成,造成内存泄漏等问题

构造函数语义

Defualt Constructor

当编译器需要条件,并不是所有的未定义构造函数的类,编译器都会合并出一个默认的构造函数),default constructor会被合成出来,只执行编译器所需要的任务。

在合成的default constructor中,只有base class subobjects(子对象)和member class objects会被初始化。所有其他的nonstatic data member,如整数,整数指针,整数数组等是不会被初始化的,这些初始化操作对程序是必须的,但对编译器则并非需要的


CopyConstructor

Default Memberwise Initialization

如果class没有提供一个 explicit copy constructor时,当class object相同的另一个object作为初值是,其内部是以所谓的default memberwiseinitialization方式完成的。也就是把每一个内建的或派生的 data member(例如一个数组或指针)的值,从某个object拷贝一份到另一个object上,但不拷贝其具体内容。例如只拷贝指针地址,不拷贝一份新的指针指向的对象,这也就是浅拷贝,不过它并不会拷贝其中member class object,而是以递归的方式实行memberwiseinitialization

这种递归的memberwiseinitialization是如何实现的呢?

答案就是Bitwise CopySemanticsdefault copyconstructor。如果class展现了Bitwise Copy Semantics,则使用bitwise copybitwise copy semantics编译器生成的伪代码是memcpy函数),否则编译器会生成default copyconstructor

以上的意思就是:当无法进行bitwise copies(浅拷贝)时,编译器才会合成一个默认的copy constructor(深拷贝)

一个良好的编译器可以为大部分class objects产生bitwise copies,因为它们有bitwise semantics...

值拷贝和位拷贝  深拷贝和浅拷贝

 

《深度探索C++对象模型》中,说class不展现出“bitwise copysemantics”有四种情况:

·        1,当class含有member object并且后者有一个copy constructor(声明或合成)

·        2,当class继承一个base class 而后者存在一个copy constructor的时候

·        3,当class声明了一个或多个virtual functions

·        4,当class派生自一个继承串链,其中有一个或多个virtual base classes

其实主要都是担心,指针在bitwise semantics下,随便复制可能会导致不可预料的错误

Data语义学

使用class封装之后,class并没有增加成本,data members直接内含在每一个class object之中,就像C struct一样。而member functions虽然包含在class声明之中,但是不出现在object中,每一个non-inline function只会产生一个函数实体。inline function会在每一个调用的地方产生一个函数实体

加上多态

这种情况会带来空间和存取时间的额外负担:

1.导入一个和virtual table,用来存储它所声明的每一个virtual functions的地址。

2.在每一个class object中导入一个vptr,提供执行期的链接,使每一个object能够找到相应的virtual table

3.加强constructor,使它能够为vptr设定初始值,让它指向class所对应的virtual table

4.加强destructor,使它能够消抹指向class相关virtual table”vptr

Function语义学

Nonstatic Member Functions

实际上编译器是将member function被内化为nonmember的形式,经过下面转化步骤:

1.给函数添加额外参数——this

2.将对每一个nonstaitc data member的存取操作改为this指针来存取。

3.member function重写成一个外部函数。对函数名精选mangling处理,使之成为独一无二的语汇。

 

 

 


原创粉丝点击