自己写的解答,如有错误之处,烦请在评论区指正!
33
如果不使用引用类型的话,Folder 类的对象可能很大,实参到形参的拷贝会影响性能。不用 const 是因为在函数内部需要对 Folder 对象进行修改
34
#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include <set>
using std::set;
class Message;
class Folder {
public:
void addMsg(Message* m);
void remMsg(Message* m);
void display() const;
void showFirstMessageAddress() const {
if (!messages.empty())
cout << *messages.begin() << endl;
}
private:
set<Message*> messages;
};
class Message {
friend void swap(Message& lhs, Message& rhs);
public:
explicit Message(const string& str = "") :
contents(str) { }
Message(const Message& m) :
contents(m.contents), folders(m.folders) {
add_to_Folders(m);
}
Message& operator=(const Message& rhs) {
remove_from_Folders();
contents = rhs.contents;
folders = rhs.folders;
add_to_Folders(rhs);
return *this;
}
~Message() {
remove_from_Folders();
}
void save(Folder&);
void remove(Folder&);
void display() const {
cout << contents << endl;
}
private:
string contents;
set<Folder*> folders;
void add_to_Folders(const Message&);
void remove_from_Folders();
};
inline void Folder::addMsg(Message* m) {
messages.insert(m);
}
inline void Folder::remMsg(Message* m) {
messages.erase(m);
}
inline void Folder::display() const {
for (const auto m : messages)
m->display();
}
inline void Message::save(Folder& f) {
folders.insert(&f);
f.addMsg(this);
}
inline void Message::remove(Folder& f) {
folders.erase(&f);
f.remMsg(this);
}
inline void Message::add_to_Folders(const Message& m) {
for (auto f : m.folders)
f->addMsg(this);
}
inline void Message::remove_from_Folders() {
for (auto f : folders)
f->remMsg(this);
}
void swap(Message& lhs, Message& rhs) {
using std::swap;
for (auto f : lhs.folders)
f->remMsg(&lhs);
for (auto f : rhs.folders)
f->remMsg(&rhs);
swap(lhs.folders, rhs.folders);
swap(lhs.contents, rhs.contents);
for (auto f : lhs.folders)
f->addMsg(&lhs);
for (auto f : rhs.folders)
f->addMsg(&rhs);
}
35
Folder 类的对象中不会正确更新包含的 Message 信息。
36
见 34 题代码
37
void addFld(Folder* f) {
folders.insert(f);
}
void remFld(Folder* f) {
folders.erase(f);
}
38
拷贝和交换的方式是一种通用的赋值运算符写法,能保证语义正确,并且自赋值不会出问题,但是相比现在的写法效率较低。
39
#include <iostream>
using std::cout;
using std::endl;
#include <memory>
using std::allocator;
#include <string>
using std::string;
#include <utility>
using std::pair;
class StrVec {
public:
StrVec() :
elements(nullptr), first_free(nullptr), cap(nullptr) {}
StrVec(const StrVec& s) {
auto newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec& operator=(const StrVec& rhs) {
auto data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
~StrVec() {
free();
}
void reserve(const size_t newcapacity) {
auto newdata = alloc.allocate(newcapacity);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
void resize(const size_t newSize) {
if (newSize <= size()) {
first_free = elements + newSize;
} else {
reserve(newSize);
while (first_free != elements + newSize) {
alloc.construct(first_free++, "");
}
}
}
void push_back(const string& s) {
chk_n_alloc();
alloc.construct(first_free++, s);
}
size_t size() const {
return first_free - elements;
}
size_t capacity() const {
return cap - elements;
}
string* begin() const {
return elements;
}
string* end() const {
return first_free;
}
void display() const {
for (auto it = begin(); it != end(); ++it)
cout << *it << endl;
}
private:
static allocator<string> alloc;
void chk_n_alloc() {
if (size() == capacity())
reallocate();
}
pair<string*, string*> alloc_n_copy(const string* b, const string* e) {
auto data = alloc.allocate(e - b);
return { data, uninitialized_copy(b, e, data) };
}
void free() {
if (elements) {
for (auto p = first_free; p != elements; )
alloc.destroy(--p);
alloc.deallocate(elements, cap - elements);
}
}
void reallocate() {
auto newcapacity = size() ? 2 * size() : 1;
auto newdata = alloc.allocate(newcapacity);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
string* elements;
string* first_free;
string* cap;
};
allocator<string> StrVec::alloc;
40
StrVec(const initializer_list<string>& list) {
auto newdata = alloc_n_copy(list.begin(), list.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
41
因为 first_free 是一个尾后指针,直接在原地构造即可。使用后置递增运算的话会跳过一个位置没有构造。
42
StrVec.h:
#pragma once
#include <iostream>
using std::cout;
using std::endl;
#include <memory>
using std::allocator;
#include <string>
using std::string;
#include <utility>
using std::pair;
#include <initializer_list>
using std::initializer_list;
class StrVec {
public:
StrVec() :
elements(nullptr), first_free(nullptr), cap(nullptr) {}
StrVec(const initializer_list<string>& list) {
auto newdata = alloc_n_copy(list.begin(), list.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec(const StrVec& s) {
auto newdata = alloc_n_copy(s.begin(), s.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec& operator=(const StrVec& rhs) {
auto data = alloc_n_copy(rhs.begin(), rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
~StrVec() {
free();
}
void reserve(const size_t newcapacity) {
auto newdata = alloc.allocate(newcapacity);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
void resize(const size_t newSize) {
if (newSize <= size()) {
first_free = elements + newSize;
} else {
reserve(newSize);
while (first_free != elements + newSize) {
alloc.construct(first_free++, "");
}
}
}
void push_back(const string& s) {
chk_n_alloc();
alloc.construct(first_free++, s);
}
size_t size() const {
return first_free - elements;
}
size_t capacity() const {
return cap - elements;
}
string* begin() const {
return elements;
}
string* end() const {
return first_free;
}
string& operator[] (const int index) {
return *(elements + index);
}
const string& operator[] (const int index) const {
return *(elements + index);
}
void display() const {
for (auto it = begin(); it != end(); ++it)
cout << *it << endl;
}
private:
static allocator<string> alloc;
void chk_n_alloc() {
if (size() == capacity())
reallocate();
}
pair<string*, string*> alloc_n_copy(const string* b, const string* e) {
auto data = alloc.allocate(e - b);
return { data, uninitialized_copy(b, e, data) };
}
void free() {
if (elements) {
for (auto p = first_free; p != elements; )
alloc.destroy(--p);
alloc.deallocate(elements, cap - elements);
}
}
void reallocate() {
auto newcapacity = size() ? 2 * size() : 1;
auto newdata = alloc.allocate(newcapacity);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
string* elements;
string* first_free;
string* cap;
};
allocator<string> StrVec::alloc;
CppPrimerCh13.cpp:
#include <iostream>
#include <fstream>
#include <sstream>
#include <cassert>
#include <memory>
#include <string>
#include <set>
#include <unordered_map>
#include "StrVec.h"
using namespace std;
class QueryResult {
string target;
shared_ptr<StrVec> spLines;
shared_ptr<set<int>> lineNumbers;
friend ostream& operator<< (ostream& os, const QueryResult& qr);
public:
QueryResult(string i_target, shared_ptr<StrVec> i_spLines, shared_ptr<set<int>> i_lineNumbers)
: target(i_target), spLines(i_spLines), lineNumbers(i_lineNumbers) {}
};
class TextQuery {
shared_ptr<StrVec> spLines;
shared_ptr<unordered_map<string, set<int>>> spWordToLine;
public:
explicit TextQuery(ifstream& infile) {
spLines = make_shared<StrVec>();
spWordToLine = make_shared<unordered_map<string, set<int>>>();
string buffer, wordBuffer;
int lineCnt = 0;
while (getline(infile, buffer)) {
spLines->push_back(buffer);
istringstream iss(buffer);
while (iss >> wordBuffer) {
(*spWordToLine)[wordBuffer].insert(lineCnt);
}
++lineCnt;
}
}
QueryResult query(const string& target) {
shared_ptr<set<int>> spLineNumbers = make_shared<set<int>>((*spWordToLine)[target]);
return QueryResult(target, spLines, spLineNumbers);
}
};
ostream& operator<< (ostream& os, const QueryResult& qr) {
if (qr.lineNumbers->empty()) {
cout << qr.target << " occurs 0 times" << endl;
return os;
}
cout << qr.target << " occurs " << qr.lineNumbers->size() << " times" << endl;
for (const auto& lineNum : *(qr.lineNumbers)) {
cout << "(line " << lineNum + 1 << ") ";
cout << (*qr.spLines)[lineNum] << endl;
}
return os;
}
void runQueries(ifstream& infile) {
TextQuery tq(infile);
while (true) {
cout << "enter word to look for, or q to quit: ";
string s;
if (!(cin >> s) || s == "q")
break;
cout << tq.query(s) << endl;
}
}
int main() {
string filename("D:\\repos\\CppPrimerCh12\\CppPrimerCh12\\test.txt");
ifstream infile(filename);
assert(infile.is_open());
runQueries(infile);
return 0;
}
运行结果:
enter word to look for, or q to quit: I
I occurs 9 times
(line 1) Last week I went to the theatre.
(line 2) I had a very good seat. The play
(line 3) was very interesting. I did not
(line 6) They were talking loudly. I got
(line 7) very angry. I could not hear the
(line 8) actors. I turned round. I looked
(line 11) In the end, I could not bear it.
(line 12) I turned round again. 'I can't
(line 13) hear a word!' I said angrily. '
enter word to look for, or q to quit: the
the occurs 5 times
(line 1) Last week I went to the theatre.
(line 7) very angry. I could not hear the
(line 9) at the man and the woman angrily.
(line 11) In the end, I could not bear it.
(line 14) It's none of your business,' the
enter word to look for, or q to quit: week
week occurs 1 times
(line 1) Last week I went to the theatre.
enter word to look for, or q to quit: ^Z
43
void free() {
for_each(elements, first_free, [this](string& s) {
alloc.destroy(&s);
});
alloc.deallocate(elements, cap - elements);
}
44
#pragma once
#include <iostream>
using std::ostream;
using std::cout;
using std::endl;
#include <memory>
using std::allocator;
using std::uninitialized_copy;
#include <utility>
using std::pair;
#include <algorithm>
#include <initializer_list>
using std::initializer_list;
#include <cstring>
class String {
friend ostream& operator<<(ostream& os, const String& s);
public:
String() :
elements(nullptr), first_free(nullptr), cap(nullptr) {}
String(const char* str) {
if (!str || !*str)
return;
const char* p = str;
first_free = elements = alloc.allocate(strlen(str));
while (*p) {
alloc.construct(first_free++, *p);
++p;
}
cap = first_free;
}
String(const String& str) {
auto newdata = alloc_n_copy(str.elements, str.first_free);
elements = newdata.first;
first_free = cap = newdata.second;
}
String& operator=(const String& rhs) {
auto data = alloc_n_copy(rhs.elements, rhs.first_free);
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
~String() {
free();
}
void reserve(const size_t newcapacity) {
auto newdata = alloc.allocate(newcapacity);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
void resize(const size_t newSize) {
if (newSize <= size()) {
first_free = elements + newSize;
} else {
reserve(newSize);
while (first_free != elements + newSize) {
alloc.construct(first_free++, '\0');
}
}
}
void push_back(const char ch) {
chk_n_alloc();
alloc.construct(first_free++, ch);
}
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;
}
private:
static allocator<char> alloc;
void chk_n_alloc() {
if (size() == capacity())
reallocate();
}
pair<char*, char*> alloc_n_copy(const char* b, const char* e) {
auto data = alloc.allocate(e - b);
return { data, uninitialized_copy(b, e, data) };
}
void free() {
std::for_each(elements, first_free, [this](char& c) {
alloc.destroy(&c);
});
alloc.deallocate(elements, cap - elements);
}
void reallocate() {
auto newcapacity = size() ? 2 * size() : 1;
auto newdata = alloc.allocate(newcapacity);
auto dest = newdata;
auto elem = elements;
for (size_t i = 0; i != size(); ++i)
alloc.construct(dest++, std::move(*elem++));
free();
elements = newdata;
first_free = dest;
cap = elements + newcapacity;
}
char* elements;
char* first_free;
char* cap;
};
allocator<char> String::alloc;
ostream& operator<<(ostream& os, const String& s) {
for (char* p = s.begin(); p != s.end(); ++p)
printf("%c", *p);
return os;
}
45
右值引用是必须绑定到右值的引用,并且只能绑定到一个将要销毁的对象。右值引用通过 && 获得。
返回非引用类型的函数,连同算术,关系,位以及后置递增递减运算符,都生成右值。不能将左值引用绑定到这类表达式上。
相比而言,左值持久,右值短暂,因为右值引用只能绑定到临时对象,所以所引用的对象将要被销毁并且该对象没有其他用户。
46
int f() {
return 2;
}
int main() {
vector<int> vi(100);
int&& r1 = f();
int& r2 = vi[0];
int& r3 = r1;
int&& r4 = vi[0] * f();
return 0;
}
47
String(const String& str) {
cout << "Copy construct" << endl;
auto newdata = alloc_n_copy(str.elements, str.first_free);
elements = newdata.first;
first_free = cap = newdata.second;
}
String& operator=(const String& rhs) {
cout << "Copy assignment" << endl;
auto data = alloc_n_copy(rhs.elements, rhs.first_free);
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
48
从空的 vector 开始调用 10 次 push_back,String 类被拷贝 35 次
#include "String.h"
using namespace std;
int main() {
vector<String> vec;
puts("**************************");
for (int i = 0; i < 10; ++i)
vec.push_back("Hello");
puts("**************************");
return 0;
}
49
StrVec:
StrVec(StrVec&& s) noexcept :
elements(s.elements), first_free(s.first_free), cap(s.cap) {
s.elements = s.first_free = s.cap = nullptr;
}
StrVec& operator=(StrVec&& rhs) noexcept {
if (this != &rhs) {
free();
elements = rhs.elements;
first_free = rhs.first_free;
cap = rhs.cap;
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
String:
String(String&& str) noexcept :
elements(str.elements), first_free(str.first_free), cap(str.cap) {
str.elements = str.first_free = str.cap = nullptr;
}
String& operator=(String&& rhs) {
if (this != &rhs) {
free();
elements = rhs.elements;
first_free = rhs.first_free;
cap = rhs.cap;
rhs.elements = rhs.first_free = rhs.cap = nullptr;
}
return *this;
}
Message:
Message(Message&& m) :
contents(std::move(m.contents)) {
move_Folders(&m);
}
Message& operator=(Message&& rhs) {
if (this != &rhs) {
remove_from_Folders();
contents = std::move(rhs.contents);
move_Folders(&rhs);
}
return *this;
}
50
和 48 题的代码一样,这次调用了 35 次移动构造函数
#include "String.h"
using namespace std;
int main() {
vector<String> vec;
puts("**************************");
for (int i = 0; i < 10; ++i)
vec.push_back("Hello");
puts("**************************");
return 0;
}
51
因为如果一个类既有移动构造函数,也有拷贝构造函数,编译器将使用普通的函数匹配规则来确定使用哪个构造函数。以值的方式返回一个 unique_ptr,返回的是右值,所以这里调用的是移动构造函数,不会出现问题。
52
这里确实有点绕,一开始没看看懂,把代码写了一遍才弄懂,先把相关代码贴在下面:
HasPtr.h:
#pragma once
#include<string>
class HasPtr {
friend void swap(HasPtr& a, HasPtr& b);
public:
HasPtr(const std::string& s = std::string()) :
ps(new std::string(s)), i(0) {}
HasPtr(const HasPtr& orig) :
ps(new std::string(*(orig.ps))), i(orig.i) {
cout << "copy constructor" << endl;
}
HasPtr(HasPtr&& p) noexcept : ps(p.ps), i(p.i) {
cout << "move constructor" << endl;
p.ps = nullptr;
}
HasPtr& operator=(HasPtr rhs) {
cout << "assignment" << endl;
swap(*this, rhs);
return *this;
}
~HasPtr() {
delete ps;
}
private:
std::string* ps;
int i;
};
inline
void swap(HasPtr& a, HasPtr& b) {
using std::swap;
swap(a.ps, b.ps);
swap(a.i, b.i);
}
CppPrimerCh13.cpp:
#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include "HasPtr.h"
int main() {
HasPtr hp(string("hello")), hp2(string("hi"));
cout << "**********************" << endl;
hp = hp2;
cout << "**********************" << endl;
hp = std::move(hp2);
cout << "**********************" << endl;
return 0;
}
运行结果:
**********************
copy constructor
assignment
**********************
move constructor
assignment
**********************
解释:
对于 hp = hp2; ,hp2 是一个变量,所以是一个左值,直接匹配赋值运算符,在参数传递的过程中调用了一次拷贝构造运算符。在赋值运算符中,又调用了我们自定义的 swap 函数,交换了 *this 和 rhs 的成员变量,赋值运算符结束后,局部变量 rhs 被析构。
对于 hp = std::move(hp2); ,std::move 将一个右值引用绑定到 hp2 上,std::move 可以调用拷贝构造函数或者移动构造函数,这里匹配了移动构造函数。我们自定义的移动构造函数返回一个 HasPtr 类型的值,传给了赋值运算符,后面发生的事情就和上面一样了。
可以看到下面这样的效率是更高的,但是用下面这样的移动赋值,hp2 就成为了移后源,其值不可用。
53
HasPtr.h:
#pragma once
#include<string>
class HasPtr {
friend void swap(HasPtr& a, HasPtr& b);
public:
HasPtr(const std::string& s = std::string()) :
ps(new std::string(s)), i(0) {}
HasPtr(const HasPtr& orig) :
ps(new std::string(*(orig.ps))), i(orig.i) {
cout << "copy constructor" << endl;
}
HasPtr(HasPtr&& p) noexcept : ps(p.ps), i(p.i) {
cout << "move constructor" << endl;
p.ps = nullptr;
}
HasPtr& operator=(const HasPtr& rhs) {
cout << "copy assignment" << endl;
*ps = *rhs.ps;
i = rhs.i;
return *this;
}
HasPtr& operator=(HasPtr&& rhs) noexcept {
cout << "move assignment" << endl;
if (this != &rhs) {
*ps = *rhs.ps;
i = rhs.i;
rhs.ps = nullptr;
}
return *this;
}
~HasPtr() {
delete ps;
}
private:
std::string* ps;
int i;
};
inline
void swap(HasPtr& a, HasPtr& b) {
using std::swap;
swap(a.ps, b.ps);
swap(a.i, b.i);
}
CppPrimerCh13.cpp:
#include <iostream>
using std::cout;
using std::endl;
#include <string>
using std::string;
#include "HasPtr.h"
int main() {
HasPtr hp(string("hello")), hp2(string("hi"));
cout << "**********************" << endl;
hp = hp2;
cout << "**********************" << endl;
hp = std::move(hp2);
cout << "**********************" << endl;
return 0;
}
运行结果:
**********************
copy assignment
**********************
move assignment
**********************
本来在拷贝构造的时候,因为参数不是引用的,所以多了一个参数传递过程中的拷贝赋值。
本来在移动赋值的时候,先要调用一次移动构造来匹配赋值运算符,然后再过一遍赋值运算符,现在直接调用移动赋值运算符即可。
54
试了一下,发现如果写了移动构造函数的话,拷贝构造函数就是删除的。无法运行原来的程序。
55
void push_back(const string& t) {
data->push_back(t);
}
void push_back(string&& t) {
data->push_back(std::move(t));
}
56
会无限递归,最后爆栈,程序结束
#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
#include <algorithm>
using std::sort;
class Foo {
public:
Foo sorted() &&;
Foo sorted() const&;
private:
vector<int> data;
};
Foo Foo::sorted() && {
cout << "right value limited" << endl;
sort(data.begin(), data.end());
return *this;
}
Foo Foo::sorted() const& {
cout << "left value limited" << endl;
Foo ret(*this);
return ret.sorted();
}
int main() {
Foo f;
f.sorted();
return 0;
}
57
正常运行,并且正确地进行了排序,因为加一个类型转换之后,编译器认为这是一个右值,于是调用了右值引用版本的 sort
#include <iostream>
using std::cout;
using std::endl;
#include <vector>
using std::vector;
#include <algorithm>
using std::sort;
class Foo {
public:
Foo sorted() &&;
Foo sorted() const&;
private:
vector<int> data;
};
Foo Foo::sorted() && {
cout << "right value limited" << endl;
sort(data.begin(), data.end());
return *this;
}
Foo Foo::sorted() const& {
return Foo(*this).sorted();
}
int main() {
Foo f;
f.sorted();
return 0;
}
58
见 56 和 57 的代码
|