复制控制

本文详细介绍了C++中复制构造函数、赋值操作符及析构函数的基本概念和使用场景,探讨了何时需要显式定义这些函数,并解释了合成版本的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当定义一个新类型的时候,需要显式或隐式地指定复制,赋值和撤销该类型的对象时会发生什么----这是通过定义特殊成员:复制构造函数,赋值操作符和析构函数来达到的.如果没有显式定义复制构造函数或赋值操作符,编译器通常会为我们定义.

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

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

赋值操作符可以通过制定不同类型的右操作数而重载.右操作数作为类类型时,如果没有编写相应类型的重载函数,编译器将为我们合成一个.

复制构造函数,赋值操作符和析构函数总称为复制控制.编译器自动实现这些操作,但类也可以定义自己的版本.

实现复制控制操作最困难的部分,往往在于识别何时需要覆盖默认版本.有一种特别常见的情况需要类定义自己的复制控制成员:类具有指针成员.

1.      复制构造函数

复制构造函数可用于:

△     根据另一个同类型的对象显式或隐式初始化一个对象.

△     复制一个对象,将它作为实参传给函数

△     从函数返回时复制一个对象

△     初始化顺序容器中的元素

△     根据元素初始化式列表初始化数组元素

 

C++支持两种初始化形式:直接初始化和复制初始化.复制初始化使用=符号,而直接初始化将初始化式放在圆括号中.当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数.复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象.

 

通过直接初始化和复制初始化仅在低级别优化上存在差异.然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,他们有本质的区别.

 

复制构造函数

合成复制构造函数:

如果我们没有定义复制构造函数,编译器将会为我们合成一个.合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本.

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

定义自己的复制构造函数:

复制构造函数就是接受单个类类型引用形参的构造函数.

复制构造函数的定义与其他构造函数一样:它与类同名,没有返回值,可以(而且应该)使用构造函数初始化列表初始化新创建对象的成员,可以再函数体中做任何其他必要的工作.

通常在以下情况下需要对复制对象时发生的事情加以控制:

类有一个数据成员指针,或者有成员表示在构造函数中分配其他资源;

类在创建新对象时必须做一些特定工作.

禁止复制

有些类需要完全禁止复制.

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

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

大多数类应定义复制构造函数和默认构造函数.不定义复制构造函数和/或默认构造函数,会严重局限类的使用.不允许复制的类对象只能作为引用传递给函数或从函数返回,它们不能用作容器的元素.

一般来说,最好显式或隐式定义默认构造函数和复制构造函数.只有不存在其他构造函数时才合成默认构造函数.如果定义了复制构造函数,也必须定义默认构造函数.

 

赋值操作符

重载赋值:

与复制构造函数一样,如果类没有定义自己的赋值操作符,则编译器会合成一个.

赋值是二元运算,所以该操作符函数有两个形参:第一个形参对应着左操作数,第二个形参对应右操作数.

大多数操作符可以定义为成员函数或非成员函数.当操作符为成员函数时,它的第一个操作数隐式绑定到this指针.

赋值操作符接受单个形参,且该形参是同一类类型的对象.右操作数一般作为const引用传递.

赋值操作符返回对同一类类型的引用.

例如:

class Sales_item

{

public:

     Sales_item& operator= (constSales_item &);

private:

     std::string        isbn;

     int                units_sold;

     double             revenue;

};

 

合成赋值操作符:

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

例如:

Sales_item& Sales_item::operator =(const Sales_item &rhs)

{

     isbn     = rhs.isbn;

     units_sold    = rhs.units_sold;

     revenue       = rhs.revenue;

 

     return*this;

}

 

可以使用合成复制构造函数的类通常也可以使用合成赋值操作符.

如果类需要复制构造函数,它也会需要复制操作符.

 

析构函数

何时调用析构函数:

1.      撤销类对象时会自动调用析构函数

2.      变量超出作用域时会自动撤销.因此会自动运行析构函数

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

4.      撤销一个容器时,也会运行容器中的类类型元素的析构函数,容器中的元素总是按逆序撤销.

 

何时编写显式析构函数:

许多类不需要显式析构函数,尤其是具有构造函数的类不一定需要定义自己的析构函数.

如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验规则,这个规则常称为三法则.

 

合成析构函数

编译器总是会为我们合成一个析构函数.合成析构函数按对象创建时的逆序撤销每个非static成员,因此,它按成员在类中声明的次序的逆序撤销成员.

对于类类型的每个成员,合成析构函数调用该成员的析构函数来撤销对象.

合成构造函数并不删除指针成员所指向的对象.

 

如何编写析构函数

析构函数是个成员函数,它的名字是在类名字之前加上一个代字号(~),它没有返回值,没有形参.

虽然可以为一个类定义多个构造函数,但只能提供一个析构函数,应用于类的所有对象.

即使编写了自己的析构函数,合成析构函数仍然运行.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值