欢迎访问我的博客新地址: 点击这里
初学c++ primer 4th 如有错误 望大家不麻烦指出,以免害人害己!
复制构造函数
什么是复制构造函数?
只有单个形参,且形参是对本类类型的对象的引用(常用const修饰)
什么时候使用复制构造函数?
1.根据另一个同类型的对象显式或隐式初始化一个对象
2.复制一个对象,将它作为实参传给一个函数
3.从函数返回时复制一个对象。
4.初始化一个顺序容器中的元素。
5.根据元素初始化式列表初始化数组。
(1)根据另外一个同类型的对象显示或隐式地初始化一个对象。
Example:
string null_book = “9-999-9999-9”;
编译器首先调用接受一个c风格字符串形参的string构造函数,创建临时对象,然后编译器使用string复制构造函数将null_book初始化为那个临时对象的副本。
‚string empty_copy = string();
string的默认构造函数创建了一个临时对象,然后复制构造函数使用该对象初始化empty_copy.
支持初始化的复制形式主要是为了与c用法兼容。当情况许可时,可以允许编译器跳过复制构造函数直接创建对象,但编译器没有义务这样做。
通常直接初始化与复制初始化仅在低级别优化上存在差异。然而,对于不支持复制的类型,或者使用非explicit构造函数(c++ primer 4th 12.4.4节)的时候,它们有本质的区别:
Example:
ifstream file1(“filename”); //直接初始化可以的
ifstream file2 = “filename”; /*错误:复制构造函数是私有的,由于不能复制io类型的对象,所以不能对这些类类型的对象使用复制初始化*/
Sales_item item = string (“9-999-9999-9”);
这个初始化只有在这个复制构造函数不是explicit的时候,编译器首先调用接受一个c风格字符串形参的string构造函数,创建Sales_item类的临时对象,然后编译器使用Sales_item构造函数将string初始化为那个临时对象的副本。
class Sales_item
{
public:
Sales_item (const std :: string &book = “”) : isbn(book),units_sold(0),
Revenue(0.0){}
Sales_Item(std :: istream &is);
};/*************这个版本就可以的***********************************/
class Sales_item{
public:
explicit Sales_Item (const std :: string &book = “”) : isbn(book), units_sold(0),Revenue(0.0){}
explicit Sales_Item(std :: istream &is);
};/*********************这个版本就不可以***************************/
(2)形参与返回值
众所周知,当形参为非引用类类型的时候,将复制实参的值。类似地,以非引用类型做返回值时,将返回return语句中的值的副本。
当形参或返回值为非引用的类类型时,由复制构造函数进行复制:
Example:
String make_plural (size_t ctr, const string &word, const string &ending)
{
return (ctr == 1) ? word : word + ending ;
}
(3)初始化容器元素
复制构造函数可以用来初始化顺序容器中的元素。例如,可以用表示容量的单个形参来初始化容器。先使用了默认构造函数,再使用了复制构造函数:
vector <string> svec(5);
编译器首先使用string类的默认构造函数创建一个string类型的临时值,然后使用复制构造函数将临时值复制到svec的每一个元素。
(4)构造函数与数组元素
如果采用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复 制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将 该值复制到相应的元素。
Example:
Sales_item primer_eds[]= { string(“0-201-16487-6”),string(“0-201-54848-8”),string
(“0-201-82470-1”),Sales_item()};
这个例题:前三个首先使用string构造函数创建string对象,然后使用单实参构造 函数来创建Sales_item的临时对象,再使用复制构造函数创建数组元素。第四个直接采 用默认构造函数创建临时对象,在采用复制构造函数创建数组元素。
习题
13.2 下面的第二个初始化不能编译。可以从vector的定义得出什么推断?
vector <int> v1(42);//ok:42 elements,each 0
vector <int> v2 = 42;
//error : what does this error tell us about vector?
参考答案:结论:vector容器没有公共的复制构造函数。因为第二个初始化是复制初始化,创建v2时,编译器首先调用接受一个int形参的vector构造函数,创建一个vecotor的临时对象,然后编译器使用vector复制构造函数将v2初始化为该临时对象的副本。
合成的复制构造函数
与合成的的默认构造函数不同,即使我们定义了其他构造函数,也会合成复制构造函数合成的复制构造函数(synthesized copy constructor),其行为是,执行逐个成员初始化(menberwise initialize),将新对象初始化为原对象的副本,具体是:直接复制内置类型成员的值,类类型的成员使用该类的复制构造函数进行复制。复制指针类型的时候,需要涉及到动态分配内存,毕竟不可能用同一个内存单元,因此合成的复制构造函数无法复制指针类型成员。
注:合成的复制构造函数可以复制数组,一般情况下不行。复制数组时,合成的复制构造函数将复制数组的每一个元素。
因为合成的复制构造函数采用menberwise initialize,所以可以看作这样一个和构造函数:每个数据成员在构造函数初始化列表中进行初始化。
Example:
class Sales_item{
// other members and constructors as before
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)
{} //empty body
定义自己的复制构造函数
因为用向函数传递对象和从函数返回对象,该构造函数不应该设置为explicit。对于许多类而言,合成复制构造函数只完成必要的工作。只包含类类型成员或内置类型(但不包含指针类型),无需显示地定义复制构造函数,也可以复制。
习题13.5
一下类中,哪些需要复制构造函数?
(a)包含四个float成员的Point3w类。
(b)Maxtrix类,其中,实际矩阵在构造函数中动态分配内存,在析构函数 中删除。
(c)Payroll类,在这个类中为每个对象提供唯一的ID
(d)Word类,包含一个string和一行列位置对为元素的vector。
参考解答:
一般而言,如果一个类拥有指针成员,或者在复制对象时有一些特定的工作要做,则该类需要显示对定义复制构造函数。
(a)Pointed3w类不需要显示定义复制构造函数,因为类中只有内置类型,且不包含指针类型成员,使用编译器提供的复制构造函数即可。
(b)Matrix类需要复制构造函数。因为需要涉及指针及内存的动态分配
(c)显然:Payroll 类需要复制构造函数。因为在根据已存在的Payroll对象创建其副本时,需要提供唯一的ID。
(d)Word类不需要复制构造函数。因为编译器会自动为其数据成员调用string和vector的复制构造函数。
习题13.6
复制构造函数为什么要定义为引用?
参考答案:每当以传值方式传递参数的时候,会导致调用复制构造函数,因此,如果要使用传值方式传递参数的复制构造函数,必须使用一个“不已传值方式传递参数”的复制构造函数,否则就会导致无穷递归调用复制构造函数。因此,复制构造函数的参数必须是以传地址方式传递参数pass-by-reference.