拷贝构造函数的第一个参数必须是引用类型,此参数几乎总是const的引用。拷贝构造函数在几种情况下会隐式地使用。因此,拷贝构造函数不应该是explicit的。
即使我们定义了其他构造函数,在没有拷贝构造函数时,编辑器也会为我们合成的。编辑器从给定对象中依次将每个非static成员拷贝到创建的对象中。每个成员决定了它使用何种方式进行拷贝。
类调用拷贝构造函数,数组逐个拷贝,内置类型直接拷贝。
string dots(10,'.直接初始化。
string noll_book="999999"; 拷贝初始化。
当我们使用拷贝初始化时,我们要去编译器将右侧运算对象拷贝到正在创建的对象中,如果需要可能进行类型转化。
拷贝初始化通常使用的是拷贝构造函数。但是,如果一个类有一个移动构造函数,则拷贝初始化有时会使用移动构造函数。
拷贝初始化不仅在我们用=定义变量时发生,在以下情况也会发生:
将一个对象作为实参传递给一个非引用类型的形参。
从一个返回类型为非引用类型的函数返回一个对象。
用花括号列表初始化一个数组中的元素或一个聚合类中的成员。
某些类类型还会对他们所分配的对象使用拷贝初始化。如,当我们初始化标准库容器或是调用insert或push成员时,容器会对其元素进行拷贝初始化。与之相对的,emplace用的是直接初始化。
如果拷贝构造函数的参数不是引用,那么我们将会再次调用拷贝构造函数。如此循环。
如果拷贝构造函数声明为explicit那么,不能进行隐式转换。
vector v1(10);
vector v2 = 10 ; 错误,void f(vector);/声明一个函数。
f(10); 错误:
f(vector(10));正确,从int直接构造一个临时vector
编辑器可以绕过拷贝构造函数,但不是必须。而且拷贝/移动构造函数必须是可访问的。
string null_book = 9999";
编辑器可改写为:
string null_book("9999");
某些运算符,包括赋值运算符,必须定义为成员函数。如果一个运算符是一个成员函数,其左侧运算对象就绑定到隐式的this参数。对于二元运算符,例如赋值运算符,其右侧运算对象作为显示参数传递。
为了与内置类型保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用。另外值得注意的是,标准库通常要求保存在容器中的类型要具有赋值运算符,且返回值是左侧对象的引用。
如果未定义自己的拷贝赋值运算符,编译器会为它自动生成一个合成赋值运算符,会将右侧对象的每个非static成员赋予左侧。
/等价于合成拷贝赋值运算符。
sales_data& sales_data::operator = const sales_data &rhs)
bookno =
units_sold =
revenue =
return *this;
析构函数释放对象使用的资源,并销毁对象的非static数据成员。由于析构函数不接有参数也没有返回值,对一个给定的类,只有唯一一个。
在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁。
在对象最后一次使用之后,析构函数的函数体可执行类设计者希望执行的任何收尾工作。通常,会释放对象在生存期分配的所有资源。
析构部分是隐式的。成员销毁时依赖成员的类型。销毁类会调用自己的析构函数。内置类型没有,什么也不做。
隐式销毁一个内置指针类型的成员不会delete掉它所指的对象。但是智能指针会调用自己的析构函数,在析构阶段会自动销毁。
无论何时一个对象被销毁,就会自动调用其析构函数:
变量离开其作用域时被销毁。
当一个对象被销毁时,其成员被销毁
容器(无论是标准库容器还是数组)被销毁时,其元素被销毁。
对于动态分配的对象,当对指向它的指针引用delete运算符时被销毁。
对于临时对象,当创建它的完整表达式结束时别销毁。
析构函数时自动运行的,我们无须担心资源何时被释放。
当指向一个对象的引用或指针离开作用域时,析构函数不会被执行。
当类未定义自己的析构函数时,编辑器会自己定义一个合成析构函数。
类似拷贝构造函数和拷贝赋值运算符,对于某些类,合成析构函数被用来阻止该类型的对象被销毁。如果不是这种情况,课程析构函数的函数体就为空。在(空)析构函数执行完毕后,成员会被自动销毁。
析构函数自身并不直接销毁成员。成员实在析构函数体之后隐含的析构阶段中被销毁的。析构函数体是作为成语销毁之外的另一部分而进行的。
我们可以从析构函数开始判断:如果一个类需要一个析构函数,我们几乎可以肯定它也需要一个拷贝构造函数和一个拷贝赋值运算符。
如果一个雷需要一个拷贝构造函数,几乎可以肯定它也需要一个拷贝赋值运算符。反之亦然,如果一个类需要一个拷贝赋值运算符,几乎可以肯定它也需要一个拷贝构造函数。然而,无论是需要拷贝构造函数还是拷贝赋值运算符都不必然意味着也需要析构函数。
sales_data() default;//显示的要求编辑器生成合成的版本;
当我们在雷内使用= default时,合成的函数将隐式的声明为内联函数,如果我们不需要内联应该只在定义是使用。
大多数类应该定义默认构造函数、拷贝构造函数和拷贝赋值运算符,无论是显示的还是隐式的。
有时候类必须采取某种机制阻止拷贝和赋值,如,iostream类就阻止了拷贝,以避免多个对象写入或读取相同io缓冲。
在新标准下,我们可以通过将拷贝构造函数和拷贝运算符定义成删除的函数来阻止拷贝。
删除函数是这样的,我们虽然声明了它们,但不能以任何方式使用它们。
nocopy(const nocapy& )delete;//阻止拷贝。
与=default不同,=delete必须是在函数第一次声明出现的时候。我们可以对任何函数指定delete,我们只能对编辑器可以合成的默认构造函数或拷贝控制成员使用=default。虽然删除函数主要是阻止拷贝,担当我们希望引导函数匹配过程时,删除函数有时也是很有用的。
值得注意的是我们不能定义析构函数时删除函数。
如果将析构函数定义成删除函数,我们不能定义该类的变量或临时对象。可以定义动态分配这种类型的对象。但是,不能释放这些对象。
对某些类来说,编辑器将合成的成员定义为删除的函数:
如果类的某个成员的析构函数时删除的或不可访问的(如,private),则类的合成析构函数被定义为删除的。
如果类的某个成员的拷贝构造函数时删除的或不可访问的,则合成的拷贝构造函数被定义为删除的。如果类的某个成员的析构函数是删除的或不可访问的,则类合成的拷贝构造函数也被定义为删除的。
如果类的某个成员的拷贝赋值运算是删除的或不可访问的,或类有一个const或引用成员,则类的合成拷贝赋值运算符被定义为删除的。
如果类的某个成员的析构函数是删除的或不可访问的,或一个类有一个引用成员,它么有类内初始化器,或是类有一个const成员,它没有类内初始化器且其未显示定义默认构造函数,则该类的默认构造函数被定义为删除的。
这些规则的含义是:如果一个类有数据成员不能默认构造,拷贝,复制或销毁,则对应的成员函数被定义为删除的。
一个成员有删除的或不可访问的析构函数会导致合成默认和拷贝构造函数被定义为删除的。
虽然我们可以将一个新值赋予一个引用成员,但这样做改变的是引用指向的对象的值,而不是引用本身。
在新标准发布之前,类是通过将其拷贝构造函数和拷贝赋值运算符声明为private来阻止拷贝的、为了阻止友元和成员函数进行拷贝,我们将这些拷贝控制成员声明为private的,但不定义他们。如果访问就会在编译阶段或链接阶段报错。
声明但不定义一个成员函数是合法的,对此只有一个例外。
应使用新版的=delete。
拷贝控制和资源管理有两种方式,令类的行为像一个值,令类的行为像一个指针。
/行为像值得类。
class hasptr
hasptr(const hasptr &p) :ps(new std::string(* i(
hasptr& operator = const hasptr &)
~hasptr()
private:
std::string *ps;
int i;
赋值运算符通常组合了析构函数和构造函数的操作。要保证正确的顺序执行,即使将一个对象赋予它自身,也保证正确。而且,如果有可能,我们编写赋值运算还应该是异常安全的,当异常发生时能将左侧对象置于一个有意义的状态。
/类值拷贝赋值运算符。
hasptr& hasptr::operator=(const hasptr &rhs)
auto newp = new string(* 在销毁左侧运算对象资源之前拷贝右侧运算对象。
delete ps;
ps=newp;
i=return *this;
如果写成delete ps; ps = new string(*(当是同一个对象时将报错。
引用计数的工作方式:
除了初始化对象外,每个构造函数(拷贝构造函数除外)还要创建一个引用计数,用来记录有多少对象正在创建的对象共享状态。当我们创建一个对象时,只有一个对象共享状态,因此将技术器初始化为1.
拷贝构造函数不分配新的计数器,而是拷贝给定对象的数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定对象的状态又被一个新的用户所共享。
析构函数递减计数器,指出共享状态的用户少了一个。如果计数器变为0,则析构函数释放状态。
拷贝赋值运算符递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧运算对象的计数器变为0,意味着它的共享状态没有用户了,拷贝赋值运算符就必须销毁状态。
C11学习笔记 5
参数传递时,如果是引用类型,它将绑定到对应的实参上,否者,将实参的值拷贝后赋给形参。引用可避免拷贝,如果不需要改变参数,最好定义成常量引用。和其他初始化一样,当用实参初始化时会忽略顶层const,当形参有顶层const时,传给它常量对象或非常量对象都可以。我们可以用非常量初始化一个底层const对象...
C 11学习笔记 11
动态分配的内存,只有在显式释放是,这些对象才会销毁。但是标准库中的两智能指针可以确保自动释放。除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。静态内存用来保存局部static对象 类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数没得非stat...
C 11学习笔记 16
tuple是类型pair的模板。不同tuple类型的成员类型也不同,但一个tuple可以有任意数量的成员。每个确定的tuple类型的成员数目是固定的。当我们希望将一些数据组合成单一对象,但有不想麻烦地定义一个新数据结构来表示时,tuple是非常有用的。快速而随意 的数据结构 tuple类型及其伴随类...