拷贝控制
--复制构造函数、赋值操作符
引言:
当定义一个新类型时,须要显式或隐式地指定复制、赋值和撤销该类型的对象时会发生什么– 复制构造函数、赋值操作符和析构函数的作用!
复制构造函数:具有单个形參。该形參(经常使用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数。当将该类型的对象传递给函数或者从函数返回该类型的对象时。将隐式使用复制构造函数。
析构函数:作为构造函数的互补。当对象超出作用域或动态分配的对象被删除时。将自己主动应用析构函数。
赋值操作符:与构造函数一样,复制操作符能够通过指定不同类型的右操作数而重载。右操作数为类类型的版本号比較特殊:假设我们没有编写这种版本号。则编译器将为我们合成一个。
【小心地雷】
通常,编译器为我们合成的复制构造函数函数是很精炼的---它们仅仅做必须的工作,但对于类而言。依赖于默认定义有时会导致灾难!
一、复制构造函数
仅仅有单个形參,并且该形參是对本类类型对象的引用(经常使用const修饰),这种构造函数称为复制构造函数。与默认构造函数一样,复制构造函数可由编译器隐式调用。复制构造函数可用于:
1)依据还有一个同类型的对象显式或隐式初始化一个对象。
2)复制一个对象,将它作为实參传给一个函数。
3)从函数返回时复制一个对象。
4)初始化顺序容器中的元素。
5)依据元素初始化式列表初始化数组元素。
1、对象的定义形式
对于类类型。初始化的复制形式和直接形式有所不同:直接初始化直接调用与实參匹配的构造函数。复制初始化式总是调用复制构造函数。[复制初始化首先使用指定构造函数创建一个暂时对象,然后复制构造函数将那个暂时对象复制到正在创建的对象!]
//理解下列语句的差别
string null_book = "9-99999-999-9";
string dots(10,'.');
string empty_copy = string();
string empty_direct;
对于类类型对象。仅仅有指定单个实參或显式创建一个暂时对象用于复制时,才使用复制初始化!
支持初始化的复制形式主要是为了与C的使用方法兼容,当情况许可时。能够同意编译器跳过复制构造函数函数直接创建对象,可是编译器没有义务这样做!
通常直接初始化和复制初始化仅在低级别上存在差异。然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质差别:
ifstream file1("filename"); //OK
ifstream file2 = "filename";//Error,由于IO类型的对象不能复制
Sales_item item = string("9-99999-999-9");
2、形參与返回值
当形參为非引用类型的时候,将复制实參的值。相似的,以非引用类型作返回值时,将返回return语句中值的副本。
//当形參/返回值为类类型时,由复制构造函数进行复制。
//然而该函数的形參是const的引用,因此不会复制
string make_plural(size_t,const string &,const string &);
3、初始化容器元素
//首先使用string默认构造函数创建一个暂时值来初始化svec
//然后使用复制构造函数将暂时值拷贝到svec的每一个元素
vector<string> svec(5);
【推荐】
作为一般规则,除非你想使用容器元素的默认初始值,更有效的办法是,分配一个空容器并将已知元素的值增加容器。
4、构造函数与数组元素
假设没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每一个元素。
然而,假设使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化来初始化每一个元素。依据指定值创建适当类型的元素,然后用复制构造函数将该值拷贝到相应元素:
Sales_item arrItem[] = {string("0-201-16487-6"),
string("0-201-54848-6"),
string("0-201-82470-6"),
Sales_item()
};
合成的复制构造函数
合成复制构造函数的行为是:运行逐个(非static)成员初始化,将新对象初始化为原对象的副本!
合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。数组成员的复制是个例外。尽管一般不能复制数组,但假设一个类具有数组成员,则合成复制构造函数将复制数组。
复制数组时合成复制构造函数将复制数组的每一个元素。
逐个成员初始化最简单的概念模型是,将合成复制构造函数看作这样一个构造函数:当中每一个数据成员在构造函数初始化列表中进行初始化。
class Sales_item
{
public:
Sales_item(const Sales_item &);
private:
std::string isbn;
int units_sold;
double revenue;
};
Sales_item::Sales_item(const Sales_item &orig):
isbn(orig.isbn),units_sold(orig.units_sold),revenue(orig.revenue) {}
定义自己的复制构造函数
class Foo
{
public:
Foo();
Foo(const Foo &); //复制构造函数
};
复制构造函数的形參一般是一个const引用。由于由于向函数传递对象和从函数返回对象。该构造函数一般不应设置为explicit!
对很多类而言,合成复制构造函数仅仅完毕必要的工作。仅仅包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义复制构造函数,也能够复制。
然而。有些类必须对复制对象时发生的事情加以控制。这种类经常有一个数据成员是指针或者有成员表示在构造函数中分配的其它资源,而还有一些类在创建新对象时必须做一些特定工作。
这两种情况下。都必须定义复制复制构造函数。
通常,定义复制构造函数最困难的部分在于认识到须要复制构造函数O(∩_∩)O~。仅仅要能认识到须要复制构造函数,定义构造函数一般很easy。复制构造函数的定义与其它构造函数一样:它与类同名,没有返回值,能够(并且应该)使用构造函数初始化列表初始化新创建对象的成员,能够在函数体中做不论什么其它必要工作。
//P410 习题13.4
class NoName
{
public:
NoName():pstring(new std::string),i(0),d(0){}
NoName(const NoName &temp):i(temp.i),d(temp.d)
{
pstring = new std::string;
*pstring = *(temp.pstring);
}
private:
std::string *pstring;
int i;
double d;
};
禁止复制
有些类须要全然禁止复制。
比如,iostream类就不同意复制。
假设想要禁止复制,似乎能够省略复制构造函数,然而,假设不定义复制构造函数,编译器将合成一个。
通过声明但不定义private复制构造函数能够禁止不论什么复制类类型对象的尝试:用于代码中的复制尝试将在编译时标记为错误,而成员函数和友元中的复制尝试将在链接时导致错误。
大多数类应定义复制构造函数和默认构造函数
不定义复制构造函数和/或默认构造函数,会严重局限类的使用:不同意复制的类对象仅仅能作为引用传递给函数或从函数返回,它们也不能用作容器的元素。
一般来说,最好显式或隐式定义默认构造函数和复制构造函数。仅仅有不存在其它构造函数时才合成默认构造函数。
假设定义了复制构造函数,也必须定义默认构造函数。
二、赋值操作符
与复制构造函数一样,假设类未定义自己的赋值操作符,则编译器会合成一个!
1、介绍重载赋值
重载操作符是一些函数,其名字为operator后跟着所定义的操作符的符号。因此,通过定义名为operator=的函数,我们能够对赋值进行定义。像不论什么其它函数一样,操作符函数有一个返回值和一个形參表。形參表必须具有与该操作符数目同样的形參(假设操作符是一个类成员,则包含隐式this形參)。赋值是二元运算,所以该操作符函数有两个形參:第一个形參相应着左操作数,第二个形參相应右操作数。
大多数操作符能够定义为成员函数或非成员函数。
当操作符为成员函数时,它的第一个操作数隐式绑定到this指针。
有些操作符(包含赋值操作符)必须是定义自己的类的成员。
由于赋值必须是类的成员,所以this绑定到指向左操作数的指针。因此,赋值操作符接受单个形參,且该形參是同一类类型的对象。右操作数一般作为const引用传递。
赋值操作符也返回对同一类类型的引用。
class Sales_item
{
public:
Sales_item &operator=(const Sales_item &);
};
2、合成赋值操作符
合成赋值操作符会运行逐个成员赋值:右操作数对象的每一个成员赋值给左操作数对象的相应成员。
除数组之外,每一个成员用所属类型的常规方式进行赋值。对于数组,给每一个数组元素赋值。
如:
Sales_item &Sales_item::operator=(const Sales_item &rhs)
{
isbn = rhs.isbn;
units_sold = rhs.units_sold;
revenue = rhs.revenue;
return *this;
}
3、复制和赋值常一起使用
实际上,应该将复制构造函数和赋值操作符看做一个单元,假设须要当中一个,我们差点儿也能够肯定须要还有一个!
//P412 习题13.9
NoName &NoName::operator=(const NoName &rhs)
{
if (pstring)
{
delete pstring;
}
pstring = new string();
*pstring = *(rhs.pstring);
i = rhs.i;
d = rhs.d;
return *this;
}
//习题13.10
class Employee
{
public:
typedef unsigned int num_type;
Employee(const std::string Name = "NoName"):name(Name),mark(count)
{
set();
}
Employee(const Employee &rhs):name(rhs.name),mark(count)
{
set();
}
~Employee()
{
-- count;
}
Employee &operator=(const Employee &rhs)
{
name = rhs.name;
return *this;
}
ostream &output(ostream &os)
{
os << "Name: " << name << "\t\tMark: " << mark << endl;
return os;
}
private:
std::string name;
num_type mark;
static num_type count;
void set()
{
++ count;
}
};
Employee::num_type Employee::count = 0;
版权声明:本文博客原创文章,博客,未经同意,不得转载。