13.6节练习

来源:互联网 发布:程序员经常加班吗 编辑:程序博客网 时间:2024/06/05 20:16

练习13.45 解释右值引用和左值引用的区别。

一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值。

左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的临时对象。


练习13.46 什么类型的引用可以绑定到下面的初始器上。

int f() { return 1; }vector<int> vi(100);int && ri = f();       //返回非引用类型的函数,生成右值int & r2 = vi[0];      //vi[0]具有持有状态,左值int & r3 = r1;         //变量是左值int && r4 = vi[0] * f();  //表达式求值过程中创建的临时对象是右值

练习13.48 定义一个vector<String>并在其上多次调用push_back。运行你的程序,并观察String被拷贝了多少次。

#include <iostream>#include <vector>#include <string>using namespace std;static size_t i = 1;static size_t j = 1;struct String {String(const char* s) :str(s) { cout << "used j " << j++ << endl; }String(const String&m) :str(m.str) { cout << "used i " << i++ << endl; }std::string str;};int main(){vector<String> svec;svec.push_back("Paul");svec.push_back("Pierce");svec.push_back("Mcgrady");svec.push_back("Carter");}

随着push_back的调用次数的增加,调用拷贝构造函数的次数随着一个有待探究的增长模式增加。


练习13.49 为你的StrVec、String和Message类添加一个移动构造函数和一个移动赋值运算符。

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.cap = rhs.first_free = nullptr;}return *this;}

String(String &&s) :str(s.str) noexcept{s.str.clear();}String operator=(String &&rhs) noexcept{if (this != &rhs) {str.clear();str = rhs.str;}return *this;}

Message::Message(Message &&m) :contents(std::move(m.contents)) {move_Folders(&m);}Message Message::operator=(Message &&rhs){if (this != &rhs) {remove_from_Folders();contents = std; :move(rhs.contens);move_Folders(&rhs);}return *this;}

练习13.50 在你的String类的移动操作添加打印语句,并重新运行13.6.1节的练习13.48的程序,它使用了一个vector<String>观察什么时候会避免拷贝。

#include <iostream>#include <vector>#include <string>using namespace std;static size_t i = 1;static size_t j = 1;struct String {String(const char* s) :str(s) { cout << "used char* " << j++ << endl; }String(const String&m) :str(m.str) { cout << "used String& " << i++ << endl; }String(String &&s) noexcept:str(s.str) {s.str.clear();cout << "used String&&" << endl;}String operator=(String &&rhs) noexcept{if (this != &rhs) {str.clear();str = rhs.str;cout << "used operator=String &&" << endl;}return *this;}std::string str;};int main(){vector<String> svec;svec.push_back("Paul");svec.push_back("Pierce");//svec.push_back("Mcgrady");//svec.push_back("Carter");//svec.push_back("Carter");//svec.push_back("Carter");String s1("D");svec.push_back(s1);String s2("C");svec.push_back(s2);}
当将源对象存入vector<String>时,使用了移动构造函数,避免拷贝。


练习13.51 虽然unique_ptr不能拷贝,但我们在12.1.5节中编写了一个clone函数,他以值方式返回一个uniquye_ptr。解释为什么函数是合法的,以及为什么他能正常工作。

我们可以拷贝或赋值一个将要被销毁unique_ptr。

离开函数体时候源对象的的指针所有权被转移,功能相当于使用了拷贝构造函数/移动构造函数。


13.52 详细解释478页中的HasPtr对象的赋值发什么了什么?特别是,一步一步描述hp,hp2以及HasPtr的赋值运算符中的参数rhs的值发生了什么变化。

正如书上所说的,一个使用拷贝构造函数,一个使用了移动构造函数来进行赋值运算。

rhs的内存空间均被释放,而后对它使用可能产生未定义的行为。


13.53 从底层效率的角度看,HasPtr的赋值运算符并不理想,解释为什么。为HasPtr实现一个拷贝赋值运算符和一个移动赋值运算符,并比较你的新的移动赋值运算符中执行的操作和拷贝并交换版本中执行的操作。

毫无疑问的,移动操作效率肯定比拷贝效率来得高。移动操作将已经存在的对象移动到另外一个位置,而拷贝操作必须得再一次创建对象。

HasPtr& HasPtr::operator=(const HasPtr& rhs){++*rhs.use;if (--*use == 0) {delete ps;delete use;}ps = rhs.ps;i = rhs.i;use = rhs.use;return *this;}HasPtr& operator=(HasPtr &&p) noexcept{if (this != &p) {ps = use = 0;ps = p.ps;use = p.use;i = p.i;}return *this;}
拷贝并交换操作。用了拷贝操作就不及移动操作的效率来的高。


练习13.54 如果我们HasPtr定义了移动赋值运算符,但未改变拷贝并交换运算符,会发生什么?编写代码验证你的答案。

= ,=编译并未能如愿通过。

应该是会继续使用拷贝并交换运算符。。。


练习13.55 为你的StrBlob添加一个右值引用版本的push_back。

void push_back(std::string &&t)&& { data->push_back(t); }
先前版本的push_back记得加上引用限定符。


练习13.56 如果sorted定义如下,会发生什么?

Foo Foo::sorted() const &{Foo ret(*this);return ret.sorted();}
程序没有进行排序,运行Foo ret(*this)后ret是左值,然后ret.sorted()调用本函数,进入死循环。


练习13.57 如果sorted定义如下,会发生什么?

Foo Foo::sorted() const &{return Foo(*this).sorted();}
Foo(*this)返回的是右值,然后调用右值引用的成员函数进行排序。


练习13.58 编写新版本的Foo类,其sorted函数中有打印语句,来验证你对前面两题的答案是否正确。

0 0
原创粉丝点击