13.1
拷贝构造函数:这样的一个构造函数,第一个参数是自身类类型的引用,且任何额外参数都有默认值
使用拷贝初始化的情况:
- 用=定义变量
- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
- 某些类类型对它们所分配的对象使用拷贝初始化(如初始化标准库容器或调用其insert/push成员时)
13.2
这是类Sales_data的拷贝构造函数,我们在调用该拷贝构造函数来初始化类对象时,需要拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造函数,如此无限循环。
举例:
当我们调用拷贝构造函数(即Sales_data(Sales_data rhs);)构建对象时,如:
Sales_data s1; Sales_data s2(s1); //死循环
代码首先使用s1作为实参构建rhs,然后为了构建形参rhs,使用传递给rhs的参数(s1)来构建rhs调用的构造函数的形参,然后为了构建.....
所以该函数一直在构建这个构造函数的形参,而始终都没有进入函数体
13.3
复制StrBlob时,其成员data关联的计数器会加1
复制StrBlobPtr时,由于成员是weak_ptr,弱指针复制不影响计数器
13.4
使用拷贝构造函数的地方:
Point global; Pint foo_bar(Point arg) //1 arg { //2 local Point local = arg, *heap = new Point(global); //3 heap指向的对象 *heap = local; Point pa[4]={ local, *heap }; //4 5 pa[0] pa[1] return *heap; //6 函数返回值 }
13.5
class HasPtr { string *ps; int i; public: HasPtr(const string &s = string()): ps(new string(s)), i(0) {} //*的优先级低于.的优先级 HasPtr(HasPtr &hp) : ps(new string(*hp.ps)), i(hp.i) {} };
13.6
拷贝赋值运算符:一个名为 operator= 的函数
什么时候使用:类对象与类对象之间的赋值时
合成拷贝赋值运算符完成的工作:将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员
什么时候生成合成拷贝赋值运算符:该类未定义自己的拷贝赋值运算符时
13.7
所有成员的赋值会发生。两个StrBlob中智能指针所指对象内存相同,计数器加一;两个StrBlobPtr中弱指针所指对象内存相同,计数器不变。
13.8
class HasPtr { string *ps; int i; public: HasPtr(const string &s = string()): ps(new string(s)), i(0) {} HasPtr(HasPtr &hp) : ps(new string(*hp.ps)), i(hp.i) {} HasPtr& operator=(const HasPtr &hp) { delete ps; ps = new string(*hp.ps); i = hp.i; return *this; } };
13.9
析构函数:是类的一个成员函数,函数名为~类名,没有返回值,也不接受参数
合成析构函数完成的工作:对于某些类,它被用来阻止该类型的对象被销毁;但更多的是用来释放对象使用的资源并销毁对象的非static成员
什么时候生成:该类未定义自己的析构函数时
13.10
调用StrBlob的合成析构函数释放数据成员,shared_ptr调用自己的析构函数,使计数-1
调用StrBlobPtr的合成析构函数,weak_ptr调用自己的析构函数,计数器不变
13.11
class HasPtr { string *ps; int i; public: HasPtr(const string &s = string()): ps(new string(s)), i(0) {} HasPtr(HasPtr &hp) : ps(new string(*hp.ps)), i(hp.i) {} HasPtr& operator=(const HasPtr &hp) { delete ps; ps = new string(*hp.ps); i = hp.i; return *this; } ~HasPtr() { delete ps; } };
13.12
3次,形参accum,局部对象item1、item2在离开作用域后调用自己的析构函数
trans为指向一个对象的指针,其离开作用域时,析构函数不会执行
13.13
1 #include <iostream> 2 #include <fstream> 3 #include <sstream> 4 #include <iterator> 5 #include <initializer_list> 6 #include <vector> 7 #include <string> 8 #include <cstring> 9 #include <deque> 10 #include <list> 11 #include <forward_list> 12 #include <array> 13 #include <stack> 14 #include <queue> 15 #include <algorithm> 16 #include <functional> 17 #include <map> 18 #include <set> 19 #include <cctype> 20 #include <unordered_map> 21 #include <unordered_set> 22 #include <memory> 23 #include <new> 24 25 using namespace std; 26 using namespace std::placeholders; 27 28 struct X{ 29 X() { cout << "X()" << endl; } 30 X(const X &x) { cout << "X(const X &x)" << endl; } 31 X& operator=(const X &x) 32 { 33 cout << "operator=(const X &x)" << endl; 34 return *this; 35 } 36 ~X(){ cout << "~x()" << endl; } 37 }; 38 39 int main() 40 { 41 //默认构造函数 42 X x1; 43 X *p = new X(); 44 //拷贝构造函数 45 X x2(x1); 46 X x3 = x1; 47 X *q = new X(x1); 48 //拷贝赋值运算符 49 x3 = x2; 50 //析构函数 51 delete p; 52 delete q; 53 return 0; 54 }
13.14
输出三个相同的序号
因为合成的拷贝构造函数只是将参数的成员逐个拷贝到正在创建的对象中,所以三个对象的mysn成员的值都相同
13.15
会改变,因为numbered类有自己的拷贝构造函数,而这个拷贝构造函数能生成一个新的序号。
所以,执行numbered b = a时,会调用拷贝构造函数,创建对象b;创建对象c也是如此。
新的输出结果是:三个不同的序号
补充:执行f(a)时,也会执行拷贝构造函数。
13.16
结果不变,因为传递的参数是常量引用,仍然会得到该实参对象的mysn值
结果会变,因为实参变为常量引用类型,故调用f()时无需拷贝,那么拷贝构造函数也不会执行。
新的输出结果是:三个不同的序号
13.17
1 #include <iostream> 2 #include <fstream> 3 #include <sstream> 4 #include <iterator> 5 #include <initializer_list> 6 #include <vector> 7 #include <string> 8 #include <cstring> 9 #include <deque> 10 #include <list> 11 #include <forward_list> 12 #include <array> 13 #include <stack> 14 #include <queue> 15 #include <algorithm> 16 #include <functional> 17 #include <map> 18 #include <set> 19 #include <cctype> 20 #include <unordered_map> 21 #include <unordered_set> 22 #include <memory> 23 #include <new> 24 25 using namespace std; 26 using namespace std::placeholders; 27 28 class numbered{ 29 friend void f(numbered); 30 int mysn; 31 public: 32 numbered() 33 { 34 static int val = 0; 35 ++val; 36 mysn = val; 37 } 38 numbered(numbered &num){ mysn = num.mysn + 5; } 39 }; 40 41 void f(numbered s) 42 { 43 cout << s.mysn << endl; 44 } 45 46 int main() 47 { 48 numbered a, b = a, c = b; 49 f(a); 50 f(b); 51 f(c); 52 return 0; 53 } 54 55
13.18
class Employee{ static size_t val; //static静态成员数据在类内声明,但只可以在类外定义,在类外定义时可不加static string name; size_t num; public: Employee() { num = ++val; } Employee(string s): name(s) { num = ++val; } };
13.19
不需要,因为没有两个雇员的姓名和雇员证号相同。
Employee(const Employee &) = delete; Employee& operator=(const Employee &) = delete;
13.21
知识点:判断一个类是否需要拷贝控制函数成员,首先判断其是否需要自定义版本的析构函数,如果需要,则拷贝控制成员函数都需要。
不需要,因为这两个类中的指针为智能指针,可以自动控制内存的释放,所以使用类的合成析构函数即可。
13.22
class Hasptr { public: //默认构造函数 Hasptr(const string &s = string()): ps(new string(s)), i(0) {} //拷贝构造函数,完成string 指针指向内容的拷贝和i值的拷贝 Hasptr(const Hasptr &hp): ps(new string(*hp.ps)),i(hp.i) {} //拷贝赋值运算符 Hasptr& operator=(const Hasptr &p) { //防止p和*this是同一个对象 auto newp = new string(*p.ps); delete ps; ps = newp; i = p.i; return *this; } //析构函数 ~Hasptr(){ delete ps; } private: string *ps; int i; };
13.23
默认构造函数也是要创建一个动态内存对象的
防范自赋值操作
13.24
未定义析构函数:会使ps指向的内存无法释放,造成内存泄漏
未定义拷贝构造函数:拷贝数据成员ps,只会拷贝指针本身
13.25
因为希望是StrBlob的类值版本,所以涉及动态分配内存。
拷贝构造函数和拷贝赋值运算符:为调用对象动态分配内存,而不是与实参共享内存
动态分配的内存由shared_ptr管理,其析构函数会自动判断进行释放,所以不需要自定义版本的析构函数。
13.26
class StrBlob { public: using size_type = vector<string>::size_type; StrBlob(); StrBlob(initializer_list<string> il); //13.26 StrBlob(const StrBlob&); StrBlob& operator=(const StrBlob&); //over size_type size() const { return data->size(); } bool empty() const { return data->empty(); } void push_back(const string &s); void pop_back(); string& front(); string& back(); const string& front() const; const string& back() const; StrBlobPtr begin(); StrBlobPtr end(); private: shared_ptr<vector<string>> data; void check(size_type i, const string &msg) const; }; //StrBlob::StrBlob(const StrBlob &rhs): data(make_shared<vector<string>>(*rhs.data)) StrBlob::StrBlob(const StrBlob &rhs): data(new vector<string>(*rhs.data)) //也可以像上面那样 { } StrBlob& StrBlob::operator=(const StrBlob &rhs) { data = make_shared<vector<string>>(*rhs.data); return *this; }
13.27
class HasPtr { public: //默认构造函数 HasPtr(const string &s = string()): ps(new string(s)), i(0), use(new size_t(1)) {} //拷贝构造函数,完成string 指针指向内容的拷贝和i值的拷贝 HasPtr(const HasPtr &hp): ps(hp.ps), i(hp.i), use(hp.use) { ++*use; } //拷贝赋值运算符 HasPtr& operator=(const HasPtr &); //析构函数 ~HasPtr(); private: string *ps; int i; size_t *use; }; HasPtr& HasPtr::operator=(const HasPtr &p) { ++*p.use; if(--*use == 0) { delete ps; delete use; } ps = p.ps; i = p.i; use = p.use; return *this; } HasPtr::~HasPtr() { if(--*use == 0) { delete ps; delete use; } }
13.29
inline void swap(HasPtr &lhs, HasPtr &rhs) { using std::swap; swap(lhs.ps, rhs.ps); //交换指针,而不是string数据 swap(lhs.i, rhs.i); //交换int成员 }
如果存在类型特定的swap版本,swap调用会与之匹配;如果不存在,则会使用std中的版本。
此swap()函数体中的swap()函数并不会递归调用自身,而是调用标准库中的swap()函数。
13.30
1 class Hasptr 2 { 3 public: 4 Hasptr(const string &s = string()): ps(new string(s)), i(0) {} 5 Hasptr(const Hasptr &hp): ps(new string(*hp.ps)),i(hp.i) {} 6 /* 7 Hasptr& operator=(const Hasptr &p) 8 { 9 //防止p和*this是同一个对象 10 auto newp = new string(*p.ps); 11 delete ps; 12 ps = newp; 13 i = p.i; 14 return *this; 15 } */ 16 ~Hasptr(){ delete ps; } 17 //13.30 18 friend void swap(Hasptr &, Hasptr &); 19 Hasptr& operator=(Hasptr p) //此处调用拷贝构造函数会分配空间 20 { 21 swap(*this, p); 22 return *this; 23 } 24 private: 25 string *ps; 26 int i; 27 }; 28 29 inline void swap(Hasptr &hp1, Hasptr &hp2) 30 { 31 cout << "swap() function is being used. "; 32 swap(hp1.ps, hp2.ps); 33 swap(hp1.i, hp2.i); 34 } 35 36 int main() 37 { 38 Hasptr hp1("kzw"), hp2, hp3; 39 swap(hp1, hp2); 40 hp3 = hp2; 41 return 0; 42 }
13.31
1 #include <iostream> 2 #include <vector> 3 #include <string> 4 #include <algorithm> 5 #include <new> 6 7 using namespace std; 8 9 class Hasptr 10 { 11 friend void swap(Hasptr&, Hasptr&); 12 friend ostream& operator<<(ostream &os, const Hasptr &hp); 13 public: 14 Hasptr(const string &s = string()): ps(new string(s)), i(0) {} 15 Hasptr(const Hasptr &hp): ps(new string(*hp.ps)),i(hp.i) {} 16 ~Hasptr(){ delete ps; } 17 Hasptr& operator=(Hasptr p) 18 { 19 swap(*this, p); 20 return *this; 21 } 22 bool operator<(const Hasptr &rhs) const 23 { 24 return *ps < *rhs.ps; 25 } 26 private: 27 string *ps; 28 int i; 29 }; 30 31 inline void swap(Hasptr &hp1, Hasptr &hp2) 32 { 33 cout << "swap() function is being used. "; 34 swap(hp1.ps, hp2.ps); 35 swap(hp1.i, hp2.i); 36 } 37 38 ostream& operator<<(ostream &os, const Hasptr &hp) 39 { 40 os << *hp.ps; 41 return os; 42 } 43 44 int main() 45 { 46 Hasptr hp1("kzw"), hp2("ly"), hp3("8023"); 47 vector<Hasptr> vec{hp1, hp2, hp3}; 48 sort(vec.begin(), vec.end()); //排序赋值时会用到swap函数 49 for (auto &i : vec) 50 cout << i << " "; //输出8023 kzw ly 51 cout << endl; 52 return 0; 53 }
13.32
会,swap函数隐式地进行引用计数的增减,而且减少了我们自己对内存进行分配的操作
下面这段代码,在形参部分会利用拷贝构造函数递增*use,而函数结束会利用析构函数递减*use:
//相较于书中冗余的代码,这更简洁
HasPtr &operator=(HasPtr hp) { swap(*this, hp); return *this; }
13.33
传引用:避免了传值的拷贝操作,可以修改形参绑定的对象
不用const:函数要修改形参绑定的对象
13.34
1 class Message{ 2 friend class Folder; 3 public: 4 explicit Message(const string &str = ""): contents(str) {} 5 Message(const Message&); 6 Message& operator=(const Message&); 7 ~Message(); 8 void save(Folder&); 9 void remove(Folder&); 10 private: 11 string contents; 12 set<Folder*> folders; 13 void add_to_Folders(const Message&); 14 void remove_from_Folders(); 15 }; 16 17 void Message::add_to_Folders(const Message &m) 18 { 19 for (auto f : m.folders) 20 f->addMsg(this); 21 } 22 23 void Message::remove_from_Folders() 24 { 25 for (auto f : m.folders) 26 f->remMsg(this); 27 } 28 29 Message::Message(const Message &m): contents(m.contents), folers(m.folders) 30 { 31 add_to_Folders(m); 32 } 33 34 Message::~Message() 35 { 36 remove_from_Folders(); 37 } 38 39 Message& Message::operator=(const Message &rhs) 40 { 41 remove_from_Folders(); 42 contents = rhs.contents; 43 folders = rhs.folders; 44 add_to_Folders(rhs); 45 return *this; 46 } 47 48 void Message::save(Folder &f) 49 { 50 folders.insert(&f); 51 f.addMsg(this); 52 } 53 54 void Message::remove(Folder &f) 55 { 56 folders.erase(&f); 57 f.remMsg(this); 58 }
13.35
使用合成拷贝控制成员:拷贝构造函数不会把信息添加到Folder中;拷贝赋值运算符不会更新Folder,也不会将新信息添加到Folder中;析构函数没有清空保留在Folder中的数据
13.36
class Folder{ public: //下面四个拷贝控制成员都可以省略,它们和对应的合成版本的功能相同 Folder(); ~Folder(); Folder(const Folder&); Folder& operator=(const Folder&); //添加/删除给定Message的指针 void addMsg(Message*); void remMsg(Message*); private: set<Message*> msg; }; //Folder类定义 void Folder::addMsg(Message *m) { msg.insert(m); } void Folder::remMsg(Message *m) { msg.erase(m); }
13.37
void Message::addFold(Folder *f) { folders.insert(f); } void Message::remFold(Folder *f) { folders.erase(f); }
13.38
当涉及到动态分配内存时,拷贝并交换是一个完成该功能的精简的方式。本题中并未涉及动态分配内存。
13.39
1 #include <iostream> 2 #include <fstream> 3 #include <sstream> 4 #include <iterator> 5 #include <initializer_list> 6 #include <vector> 7 #include <string> 8 #include <cstring> 9 #include <deque> 10 #include <list> 11 #include <forward_list> 12 #include <array> 13 #include <stack> 14 #include <queue> 15 #include <algorithm> 16 #include <functional> 17 #include <map> 18 #include <set> 19 #include <cctype> 20 #include <unordered_map> 21 #include <unordered_set> 22 #include <memory> 23 #include <new> 24 #include <utility> 25 26 using namespace std; 27 using namespace std::placeholders; 28 29 class StrVec { 30 public: 31 StrVec(): elements(nullptr), first_free(nullptr), cap(nullptr) {} 32 StrVec(const StrVec&); 33 StrVec &operator=(const StrVec&); 34 ~StrVec(); 35 void push_back(const string&); 36 size_t size() const { return first_free - elements; } 37 size_t capacity() const { return cap - elements; } 38 string *begin() const { return elements; } 39 string *end() const { return first_free; } 40 void reallocate_my(size_t newcapacity); 41 void reserve(size_t n); 42 void resize(size_t n); 43 private: 44 allocator<string> alloc; 45 void chk_n_alloc() 46 { 47 if (size() == capacity()) 48 reallocate(); 49 } 50 pair<string*, string*> alloc_n_copy(const string*, const string*); 51 void free(); 52 void reallocate(); 53 string *elements; 54 string *first_free; 55 string *cap; 56 }; 57 58 void StrVec::push_back(const string &s) 59 { 60 chk_n_alloc(); 61 alloc.construct(first_free++, s); 62 } 63 64 pair<string*, string*> StrVec::alloc_n_copy(const string *b, const string *e) 65 { 66 auto data = alloc.allocate(e - b); 67 return {data, uninitialized_copy(b, e, data)}; 68 } 69 70 void StrVec::free() 71 { 72 if (elements) { 73 for (auto p = first_free; p != elements;) 74 alloc.destroy(--p); 75 alloc.deallocate(elements, cap - elements); 76 } 77 } 78 79 StrVec::StrVec(const StrVec &s) 80 { 81 auto newdata = alloc_n_copy(s.begin(), s.end()); 82 elements = newdata.first; 83 first_free = cap = newdata.second; 84 } 85 86 StrVec::~StrVec() 87 { 88 free(); 89 } 90 91 StrVec &StrVec::operator=(const StrVec &rhs) 92 { 93 auto data = alloc_n_copy(rhs.begin(), rhs.end()); 94 free(); 95 elements = data.first; 96 first_free = cap = data.second; 97 return *this; 98 } 99 100 void StrVec::reallocate() 101 { 102 auto newcapacity = size() ? 2 * size() : 1; 103 auto newdata = alloc.allocate(newcapacity); 104 auto dest = newdata; 105 auto elem = elements; 106 for (size_t i = 0; i != size(); ++i) 107 alloc.construct(dest++, move(*elem++)); 108 free(); 109 elements = newdata; 110 first_free = dest; 111 cap = elements + newcapacity; 112 } 113 114 void StrVec::reallocate_my(size_t newcapacity) 115 { 116 auto newdata = alloc.allocate(newcapacity); 117 auto dest = newdata; 118 auto elem = elements; 119 for (size_t i = 0; i != size(); ++i) 120 alloc.construct(dest++, move(*elem++)); 121 free(); 122 elements = newdata; 123 first_free = dest; 124 cap = elements + newcapacity; 125 } 126 127 void StrVec::reserve(size_t n) 128 { 129 if (n > capacity()) 130 reallocate_my(n); 131 } 132 133 void StrVec::resize(size_t n) 134 { 135 if (n > size()) { 136 if (n > capacity()) 137 reserve(2 * n); 138 for (size_t i = size(); i != n; ++i) 139 alloc.construct(first_free++); //构造空串 140 } 141 else { 142 while (first_free != elements + n) { 143 alloc.destroy(--first_free); 144 } 145 } 146 } 147 148 int main() 149 { 150 return 0; 151 }
13.40
StrVec::StrVec(initializer_list<string> il) { auto newdata = alloc_n_copy(il.begin(), il.end()); elements = newdata.first; cap = first_free = newdata.second; }
13.41
first_free指向的是一个空位置,前置的话会跳过一个空位置。
13.43
for_each(elements, first_free, [this](string &rhs){ alloc.destroy(&rhs); });
两种实现各有千秋,我都喜欢
13.44
class String { friend ostream &operator<<(ostream &os, const String &s); public: String(): elements(nullptr), first_free(nullptr), cap(nullptr) {} String(const char*); String(const String&); String& operator=(const String&); ~String(){ free(); } size_t size()const{return first_free-elements;} //返回已占用的大小 size_t capacity()const{return cap-elements;} //返回总大小 char *begin()const{return elements;} char *end()const{return first_free;} String &operator=(const String&); String &operator=(const char*); private: allocator<char> alloc; void chk_n_alloc() { if (size() == capacity()) reallocate(); } pair<char*, char*> alloc_n_copy(const char*, const char*); void free(); void reallocate(); string *elements; string *first_free; string *cap; };
13.45
右值引用:绑定到右值的引用,通过&&来获得右值引用,只能绑定到一个将要销毁的对象
左值引用:绑定到左值的引用,通过&来获得左值引用
13.46
知识点:
右值引用可以绑定到这类表达式:要求转换的表达式、字面常量、返回右值的表达式(返回非引用类型的函数,算术、关系、位以及后置递增/递减运算符)
左值引用可以绑定到这类表达式:返回左值的表达式(返回左值引用的函数,赋值、下标、解引用以及前置递增/递减运算符)
变量是左值,故不能将一个右值引用绑定到一个变量上,即使这个变量是右值引用类型
int &&r1 = f(); int &r2 = vi[0]; int &r3 = r1; //r1是变量 int &&r4 = vi[0] * f(); //右侧是算术表达式
13.49
StrVec(StrVec &&s); StrVec &operator=(StrVec &&s); String(String &&s); String &operator=(String &&s); Message(Message &&m); Message &operator=(Message &&m);
13.50
返回左值时会避免拷贝
13.51
因为将要被销毁,智能指针离开作用域后销毁,而返回值返回后也会销毁
调用clone返回的是一个右值,所以赋值时会用移动操作而不是拷贝
13.52
调用拷贝构造rhs、把rhs与调用对象执行swap交换、返回后rhs被销毁
13.53
参数为右值,交换不必要
13.54
当使用h1=std:move(h2);时有多个运算符“=”匹配,除非拷贝赋值运算符是const &
13.55
void StrBlob::push_back(string &&s) { data->push_back(std::move(s)); }
13.56
ret是左值,当返回ret.sorted()时,调用的是Foo Foo::sorted() const &;,而不是Foo Foo::sorted() &&;,所以会进入死循环,导致堆栈溢出
13.57
拷贝构造一个临时对象,调用sorted()&&,返回时调用移动构造函数Foo Foo::sorted() &&;
13.58
#include <iostream> #include <fstream> #include <sstream> #include <iterator> #include <initializer_list> #include <vector> #include <string> #include <cstring> #include <deque> #include <list> #include <forward_list> #include <array> #include <stack> #include <queue> #include <algorithm> #include <functional> #include <map> #include <set> #include <cctype> #include <unordered_map> #include <unordered_set> #include <memory> #include <new> #include <utility> using namespace std; using namespace std::placeholders; class Foo { public: Foo sorted() && ; Foo sorted() const &; private: vector<int> data; }; Foo Foo::sorted() && { cout << "&&" << endl; sort(data.begin(), data.end()); return *this; } Foo Foo::sorted() const & { cout << "const &" << endl; Foo ret(*this); return ret.sorted(); //函数将导致运行时堆栈溢出 } int main() { using namespace std; Foo f; f.sorted(); //无限递归 std::move(f).sorted(); //调用sorted()&& return 0; }