练习13.1 拷贝构造函数是什么?是什么时候使用它?
接受对象类型本身的引用的构造函数。当要求编译器将右侧运算符对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。
练习13.2 解释为什么下面的声明是非法的:
Sales_data::Sales_data(Sales_data rhs);
rhs要拷贝给参数匹配的构造函数,此时需要调用拷贝构造函数,然后得调用上述函数,如此无限循环。
练习13.3 当我们拷贝一个StrBlob时,会发生什么?拷贝一个StrBlobPtr呢?
拷贝一个是shared_ptr类的StrBlob,引用计数会加1。
拷贝一个是weak_ptr类的StrBlobPtr,引用计数不改变。
练习13.4 假定Point是一个类类型,它有一个public的拷贝构造函数,指出下面程序片段中哪些地方使用了拷贝构造函数:
Point global;
Point foo_bar(Point arg)//将一个对象作为实参传递给一个非引用类型的形参
//以及从一个返回类型为非引用类型的函数返回一个对象
{
Point local = arg, *heap = new Point(global); //用=定义变量
*heap = local;
Point pa[4] = { local,*heap }; //用花括号列表初始化一个数组中的元素
return *heap;
}
class HasPtr {
public:
HasPtr(const std::string &s = std::string()) :ps(new std::string(s)), i(0) {}
HasPtr(const Hasptr& s2) :ps(new std::string(*s2.ps)), i(s2.i) {}
private:
std::string *ps;
int i;
};
拷贝赋值运算符控制对象如何赋值。
赋值运算符通常用来返回一个指向其左侧运算对象的引用。
合成拷贝赋值运算符用来禁止该类型对象的赋值。
如果一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符。
练习13.7 当我们将一个StrBlob赋值给另一个StrBlob时,用发生什么?赋值StrBlobPtr呢?
引用计数值加1,两个对象指向同一个。
被赋值的StrBlobPtr弱共享右侧StrBlob,引用计数未改变。
练习13.8 为13.1.1节练习中的HasPtr类编写赋值运算符。类似拷贝构造函数,你的赋值运算符应该将对象拷贝到ps指向的位置。
HasPtr& operator=(const HasPtr& rhs)
{
ps = new std::string(*rhs.ps);
i = rhs.i;
return *this;
}
析构函数是用来释放对象使用的资源,并销毁对象的非静态数据成员。
合成析构函数被用来阻止该类型的对象被销毁。如果不是这种情况,合成析构函数的函数体就为空。
当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。
练习13.10 当一个StrBlob对象销毁时会发生什么?一个StrBlobPtr对象销毁时呢?
StrBlob对象的引用计数减一,若引用计数为0,对象被销毁。
而StrBlobPtr的对象不再享有管理控制权,引用计数不变。
练习13.11 为前面练习的HasPtr类添加一个析构函数。
~HasPtr() { delete ps; }
#include
#include
using namespace std;
static int i = 0;
class Sales_data {
public:
Sales_data() = default;
Sales_data(const std::string &s) :book(s){}
~Sales_data() { cout << "used Sales_data: "<< ++i << endl; }
const std::string isbn() const { return book; }
private:
std::string book;
};
bool fcn(const Sales_data *trans, Sales_data accum)
{
Sales_data item1(*trans), item2(accum);
return item1.isbn() != item2.isbn();
}
int main()
{
Sales_data m1;
Sales_data m2;
cout << fcn(&m1, m2) << endl;
}
在题中指定的代码段中会调用三次析构函数。
trans离开作用域不会执行析构函数(指向一个对象的引用或指针离开作用域)。
整段代码一共调用五次析构函数。
练习13.13 理解拷贝控制成员和构造函数的一个好方法是定义一个简单的类,为该类定义这些成员,每个成员都打印自己的名字:
struct X {
X() { std::cout << "X()" << std::endl; }
X(const X&) { std::cout << "X(const X&)" << std::endl; }
};
给X添加拷贝赋值运算符和析构函数,并编写一个程序以不同方式使用X的对象:
将它们作为非引用和引用参数传递;
动态分配它们;
将它们存放于容器中;
诸如此类。观察程序的输出,直到你确认理解了什么时候用使用拷贝控制成员,以及为什么会使用它们。当你观察程序输出时,记住编译器可以略过对拷贝构造函数的调用。
#include
#include
#include
using namespace std;
struct X {
X() { std::cout << "X()" << std::endl; } //构造函数
X(const X&) { std::cout << "X(const X&)" << std::endl; } //拷贝构造函数
~X() { cout << "used X()" << endl; } //析构函数
X& operator= (const X&) { cout << "X& operator" << endl; return *this; } //赋值运算符
};
void func_1(X) {};
void func_2(X&) {};
int main()
{
X a1; //直接初始化,下面三个调用拷贝构造函数
X a2 = a1;
X a3(a1); //类类型使用拷贝初始化
X a4{ a1 };
cout << "test 2" << endl;
func_1(a1); //编译器略过对拷贝构造函数的调用
func_2(a2); //拷贝构造函数
cout << "test 3" << endl;
X* p = new X[5]; //输出5个构造函数X()
delete[]p;
cout << "test 4" << endl;
vector xvec;
xvec.push_back(a1); //容器调用拷贝构造函数
a3 = a1;
}
练习13.14 假定numbered是一个类,它有一个默认构造函数,能为每个对象生成一个唯一的序号,保存在名为mysn的数据成员中。假定numbered使用合成的拷贝控制成员,并给定如下函数:
#include
using namespace std;
static int i = 7;
struct numbered {
numbered() { ++i ; }
int mysn = i;
};
void f(numbered s)
{
cout << s.mysn << endl;
}
int main()
{
numbered a, b = a, c = b;
numbered d;
f(a);
f(b);
f(c);
f(d);
}
f(a),f(b),f(c)会输出相同的结果。
f(d) 则不同。
练习13.15 假定numbered定义了一个拷贝构造函数,能生成一个新的序号。这会改变上一题中调用的输出结果吗?如果会改变,为什么?新的输出结果是什么?
numbered(const numbered&) { ++i; } //新添加的拷贝构造函数
会改变,因为拷贝初始化在用=定义变量时候会发生,以及传递非引用实参。
练习13.16 如果f中的参数是const numbered& 将会怎样?还会改变输出结果吗?如果会改变,为什么?新的输出结果是什么?
void f(const numbered &s)
{
cout << s.mysn << endl;
}
传递引用实参不会调用拷贝函数,会影响到mysn的数值。
练习13.17分别编写前三题中所描述的numbered和f,验证输出结果。
(编译器可能略过拷贝构造函数,最终输出结果可能与预想的有偏差)
练习13.18 定义一个Employee类,它包含雇员的姓名和唯一的雇员证号。为这个类定义默认构造函数,以及接受一个表示雇员姓名的string的构造函数。每个构造函数应该通过递增一个static数据成员来生成唯一的证号。
#include
#include
using namespace std;
static int isbn = 1;
struct Employee {
Employee() = default;
Employee(const std::string s) :name(s) ,i(isbn){ ++isbn; }
std::string name;
int i = isbn;
};
int main()
{
Employee name1("Rose");
Employee name2("Irving");
cout << name1.name << " " << name1.i << endl;
cout << name2.name << " " << name2.i << endl;
}
。。。需要拷贝赋值运算符。
因为有个同名的。
#include
#include
using namespace std;
static int isbn = 1;
struct Employee {
Employee() = default;
Employee(const std::string s) :name(s), i(isbn) { ++isbn; }
Employee& operator=(const Employee& m)
{
name = m.name;
return *this;
}
std::string name;
int i = isbn;
};
int main()
{
Employee name1("Rose");
Employee name2("Irving");
Employee name2_ = name2;
name2_.i = 20;
cout << name1.name << " " << name1.i << endl;
cout << name2.name << " " << name2.i << endl;
cout << name2_.name << " " << name2_.i << endl;
}