13.1.1拷贝构造函数
| 拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符 析构函数。拷贝控制操作 合成拷贝构造函数 拷贝初始化 Explicit Foo(const Foo&); String dot(10,’.’))//直接初始化 String s(dot);//直接初始化 String s3 = s2;//拷贝 String s4 = “999”;//拷贝 String s5 = string(100,’9’);拷贝 F(vector<int>(10)); |
1. 拷贝、赋值与销毁 1)定义一个类时,显式或隐式地指定在此类型的对象拷贝、移动、赋值和销毁时做什么。通过五种特殊的成 员函数来控制这些操作 2)一个类没有定义的话,编译器会合成,但是对于一些类来说,使用合成的版本不好,并非我们所需 2. 拷贝构造函数 当用同类型的另一个对象初始化本对象时做什么,就是在初始化的时候,右侧对象可以先隐式转换对象再通过 = 赋给左侧运算对象 1)拷贝构造函数的第一个参数必须是一个引用类型且任何额外参数都有默认值,并且不应该是explicit的因为 需要隐式地被使用 3. 合成拷贝构造函数 1)合成的版本是将参数的成员逐个拷贝到正在创建的对象中,非static成员。 2)拷贝方式由每个成员的类型决定:类类型成员,使用拷贝构造函数,内置类型成员,直接拷贝。数组不能 直接拷贝,合成版本会逐元素地拷贝一个数组类型的成员。如果数组元素是类类型,使用元素的拷贝构造来进行拷贝 4. 拷贝初始化 1)直接初始化:使用普通的函数匹配选择匹配的构造函数 2)拷贝初始化:将右侧运算对象拷贝到正在创建的对象中,如果需要的话要进行类型转换 3)拷贝初始化不仅在 = 定义变量时发生,还有: 一个对象作为实参传给非引用类型的形参 从一个返回类型为非引用类型的函数返回一个对象 用花括号列表初始化一个数组中的元素或聚合类的成员 4)某些类类型还会对他们所分配的对象使用拷贝初始化。如:容器使用insert或push容器会对其元素进行拷 贝初始化。相对的,用emplace成员创建的元素都进行直接初始化。 5. 参数和返回值 1)如上,非引用类型的参数要进行拷贝初始化,所以我们的拷贝构造函数的参数要是引用类型,会循环。 6. 拷贝初始化的限制 1)使用了explicit的拷贝构造函数需要显示使用构造函数。 7. 编译器可以绕过拷贝构造函数 1)Test1 t2 = Test1("sdfsd");// 这个直接构造对象,不会发生拷贝构造 2)Test1 t1("sdfsdf");// 也是直接构造对象,应该是直接使用函数,不会发生拷贝构造 3)Test1 t3 = string("sdfsd");//直接构造对象,不会发生拷贝构造 以上三个都会使用string的构造函数,不会发生拷贝构造 只有以下两种 1)Test1 t4(t1);// 显示调用拷贝构造函数 接受一个t1对象 2)Test1 t5 = t2;// 隐式调用拷贝构造函数 |
13.1.2拷贝赋值运算符
| Sal trans,accum; Trans = accum; Foo& operator=(const Foo&); 合成拷贝赋值运算符 |
1. 拷贝赋值运算符 1)是用同类型的另一个对象给当前对象赋值时,但是不是初始化,是已经定义过了的后再用同类型的另一个 对象赋值,也是用=赋值运算符 2. 重载赋值运算符 1)重载=运算符,一个返回类型和一个参数列表 2)返回一个指向其左侧运算对象的引用(*this) 3)参数列表是一个与其所在类相同类型,是引用并且是const,右侧运算对象作为显式参数传递 Note:通常应该返回一个指向其左侧运算对象的引用 3. 合成拷贝赋值运算符 1)合成拷贝赋值运算符用来禁止该类型对象的赋值。若不用用此目的,需将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员 |
13.1.3析构函数
| ~Foo(); Int *p = new int(3); Delete p; ~Sales_data(){} |
1. 析构函数 1)析构函数释放对象使用的资源,并且销毁对象的非static数据成员 2)是一个成员函数,由波浪号接类名构成,没有返回值,不接受参数 3)析构函数不接受参数,所以不能被重载。一个类只有唯一的析构函数 2. 析构函数完成什么工作 1)首先执行函数体,然后销毁成员,是初始化顺序的逆序销毁的 2)析构函数体可以执行类设计者希望执行的任何收尾工作。通常,是释放对象在生存期分配的所有资源 3)不存在初始化列表的东西控制成员如何销毁,析构部分是隐式的 4)完成依赖于成员的类型,类类型的成员需要执行成员自己的析构函数,内置类型没有析构函数,所以说都 不需做 Note: 所以需要显示销毁一个指针,智能指针是类类型,有析构函数,会自动析构 3. 什么时候会调用析构函数 1)离开作用域 2)对象被销毁,其成员被销毁 3)容器被销毁,其元素被销毁 4)动态分配的对象,delete时 5)临时对象,表达式结束 由于1)所以可以定义普通对象,但是如果是动态分配的对象,需要显示delete,才会执行析构函数 Note:当指向一个对象的引用或指针离开作用域时,不会自动执行析构 4. 合成析构函数 1)析构函数被用来阻止该类型的对象被销毁?不是这种情况,函数体为空 2)空析构函数体执行完成后,成员会被自动销毁。如:执行完析构函数体,执行stirng类型的析构函数销毁 bookno 3)析构函数体自身并不直接销毁成员,是在析构函数体之后隐含的析构阶段中被销毁的 |
13.1.4三五法则
| |
1. 三/五法则 1)并不要求定义所有这些操作,可以只定义其中一个两个,而不必定义所有。但是这些操作通常应该被看做一个整体。 2. 需要析构函数的类也需要拷贝和赋值操作 1)是否决定要拷贝控制成员,由是否需要一个析构函数决定,如果需要一个析构,则也明显要拷贝构造和赋值运算符 2)因为若类中有一个动态资源,需要显示delete,但如果默认的控制操作,会将地址传给新的对象,那么执 行两个对象的析构会删除两次那个地址内存,那么就是不可取的 Note: 如果需要一个析构,则也明显要拷贝构造和赋值运算符 3. 需要析构函数的类也需要拷贝和赋值操作 1)某些类的所要完成的工作,只需要拷贝或赋值操作,不需要析构函数 2)那么拷贝或赋值就绑定在一起,若一个类需要一个拷贝构造函数,那么也要拷贝赋值运算符 3)相反,若要赋值肯定要拷贝,但不一定要析构 Note: 若一个类需要一个拷贝构造函数,那么也要拷贝赋值运算符相反,若要赋值肯定要拷贝,但不一定要析构 |
13.1.5使用=default
Sales_data()=default; Sales_data(const Sales_data&)=default; Sales_data& operator=(const Sales_data&) ~Sales_data()=default; Sales_data& Sales_data::operator=(const Sales_data&) =default; | |
1. 使用=default 1)可以通过成员定义为=default来显式要求编译器生成合成的版本 2)声明后为内联了,不想为内联就需要在类外使用=default Note: 只能对具有合成版本的成员函数使用=default 默认函数或者拷贝控制成员 |
13.1.6阻止拷贝
| Nocopy(const Nocopy&)=delete; Private: Nocopy(const Nocopy&); |
1. 阻止拷贝 1)对某些类来说拷贝赋值和拷贝构造没有合理的意义,必须采用某种机制阻止 2)如:iostream不能拷贝,若采取不定义方式是不可的,因为编译器会合成一个 2. 定义删除的函数 1)删除的函数:虽然声明了他们,但不能以任何方式使用它们 2)在函数参数列表后面加上=delete指明 3)=delete必须出现在第一次声明地方 4)适合所有函数,不同于default只适合拷贝控制 5)当我们希望引导函数匹配过程时,删除函数有时也是很有用的 3. 析构函数不能是删除的成员 1)只是规定,非要定义删除的话,不允许定义该类型的变量或创建该类的临时对象 2)但是可以动态分配,但是delete不掉 4. 合成的拷贝控制成员可能是删除的 编译器可能会将这些合成的拷贝控制成员定义为删除的 1)若类的某个成员的析构函数是删除的或者不可访问,这个类的析构函数被定义删除 2)若类的某个成员的拷贝构造函数或者析构函数是删除的或者不可访问,这个类的拷贝构造函数被定义删除 3)若类的某个成员的拷贝赋值运算符是删除的或者不可访问,或者有一个const或引用成员,则这个类合成的拷贝赋值运算符被定义删除的 4)若类的某个成员的析构函数是删除的或者不可访问,或类有一个引用成员它没有类内初始化器,或者const成员 它没有类内初始化器(在类内指定默认值)且其类型未显示定义默认构造函数则该类的默认构造函数被定义为删除的 note:如果类的某个成员不能构造、拷贝 复制或销毁,则对应的成员函数被定义为删除 5. private控制拷贝 1)新标准之前,通过将拷贝构造 赋值声明为private来阻止拷贝 2)友元和成员函数仍旧可以拷贝对象,可以通过声明不定义方式,来让链接时错误 3)尽量用delete,而不是private |