C++中的复制控制

每种类型,无论是内置类型还是类类型,都对该类型对象的一组操作的含有进行了定义。每种类型还定义了创建该类型的对象时会发生什么——构造函数定义了该类类型对象的初始化。类型还能控制复制、赋值或撤销该类型的对象时会发生什么——类通过特殊的成员函数:复制构造函数、赋值操作符和析构函数来控制这些行为。

复制构造函数是一种特殊的构造函数,具有单个形参,该形参是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用赋值构造函数。当该类型的对象传递给函数或从函数返回该类型的对象时,将隐式使用复制构造函数。

析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数 。析构函数可用于释放对象时构造或在对象的生命期中所获取的资源。不管是否定义析构函数,编译器将自动执行类中非static数据成员的析构函数。

赋值操作符,和构造函数一样,它也可以通过指定不同类型的右操作数而重载。

制构造函数、赋值操作符和析构函数总称为复制控制。

复制构造函数

只有单个形参,而且该形参是本类类型对象的引用(常用const修饰),这样的构造函数称为复制构造函数。与默认构造函数一样,复制构造函数可以由编译器隐式调用。

复制构造函数可用于:

(1)根据另一个同类型的对象显示或隐式初始化一个对象;
(2)复制一个对象,将它作为实参传递给一个函数;
(3)从函数返回时复制一个对象;
(4)初始化顺序容器中的元素;
(5)根据元素初始化式列表初始化数组元素;

对象的定义形式

C++中有两种初始化形式:复制初始化和直接初始化。复制初始化使用=符号,而直接初始化将初始化式放在圆括号中。

当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象:

对于类类型的对象,只有指定单个实参或显示创建一个临时对象用于复制时,才使用复制初始化。

注意:由于不能复制IO类型的对象,所以不能对那些类型的对象使用复制初始化。

形参和返回值

当形参为非引用类型时,将复制实参。类似的,以非引用类型作返回值时,将返回return语句中的值的副本。

当形参或返回值为类类型时,由复制构造函数进行复制。

初始化容器元素

复制构造函数可用于初始化顺序容器中的元素。例如,可以用表示容量的单个形参来初始化容器。容器的这种构造方式使用了默认构造函数和复制构造函数。

编译器首先使用string默认构造函数创建一个临时值来初始化svec,然后使用复制构造函数将临时值复制到svec的每个元素。

构造函数与数组元素

如果没有为类类型的数组提供元素初始化式,则将用默认构造函数初始化每个元素。然后,如果使用常规的花括号括住的数组初始化列表来提供显示元素初始化,则使用复制初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素。

如前三个元素的初始化中所示可以直接指定一个值,用于调用元素类型的单个形参构造函数。如果不希望指定实参或指定多个实参,就需要使用完整的构造函数语法。

合成的复制构造函数

如果没有定义复制构造函数,编译器会为我们合成一个。与合成的默认构造函数不同,即使定义了其他的构造函数,也会合成复制构造函数。
合成复制构造函数的行为:执行逐个成员初始化,将新对象初始化为原对象的副本。

所谓逐个成员,指的是编译器将现有对象的每个非static成员,依次复制到正创建的对象。只有一个例外,每个成员的类型决定了复制该成员的含义。合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。数组成员是个例外,虽然一般不能复制数组,但如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。

对许多类而言,合成复制构造函数只完成必要的工作。只包含类类型成员或内置类型(但不是指针类型)的类,无须显示地定义复制构造函数,也可以复制。

有些类必须对复制对象时发生的事情加以控制,这样的类经常有一个数据成员是指针,或者有的成员在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定的工作,这两种情况下,都必须定义复制构造函数。

禁止复制

有些类需要完全禁止复制,例如iostream类就不允许复制。

为了防止复制,类必须显式声明其复制构造函数为private。

如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。

然而,类的友元和成员仍然可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个private复制构造函数但不对其定义。

赋值操作符

重载操作符是一些函数,其名字为operator后跟着所定义的操作的符号。因此,通过定义名为operator=的函数,可以对赋值进行定义。形参表必须定义具有与该操作符操作数数目相同的形参(如果操作符为一个成员,则包括隐式this指针)。

当操作符为成员函数时,它的第一个操作数隐式绑定到this指针,有些操作符(包括赋值操作符)必须是定义自己的类的成员。

例如,Sales_item的赋值操作符可以声明为:

合成赋值操作符与合成复制构造函数操作类似,它会执行逐个成员赋值:右操作数对象的每个成员赋值给左操作数对象的对应成员。除数组之外,每个成员用所属类的常规方式进行赋值。对于数组,给每个数组元素赋值。

合成赋值操作符根据成员类型使用适合的内置或类定义的赋值操作符,依次给每个成员赋值,该操作符返回*this,它是对左操作数对象的引用。

析构函数

构造函数的一个用途就是自动获取资源。析构函数就是与构造函数配套的函数,它是一个特殊的函数,它可以完成所需资源的回收,作为类的构造函数的补充。

撤销对象时会自动调用析构函数。动态分配的对象只有在指向该对象的指针被删除时才撤销。如果没有删除指向动态对象的指针,则不会运行析构函数,对象一直存在,从而导致内存泄露,而且,对象内部使用的任何资源也不会释放。

当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。

与复制构造函数或赋值操作符不同,编译器总是会为我们合成一个析构函数。合成析构函数按对象创建时的逆序撤销,每个非static成员,因此,它按成员在类中声明的次序的逆序撤销成员。

即使我们编写了析构函数,合成析构函数仍然运行。

撤销Sales_item类型的对象时,将运行这个什么也不做的析构函数,它执行完毕后,将运行合成析构函数以撤销类的成员。

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部