41:就是前置先加後用,後置先用後加,first_free指向的是一個空位置,前置的話會跳過一個空位置。
42:本章所實作的StrVec類屬于簡化版本的容器類,隻适用于string,運作時可動态配置設定記憶體的大小
43:使用for_each和lambda表達式可能會更好一點,無需循環,語義更加明顯
void free()
{
if (elements)
{
// for (auto p = first_free; p != elements; )
// {
// alloc.destroy(--p);
// }
for_each(elements, first_free, [this](std::string &rhs){ alloc.destroy(&rhs); });
alloc.deallocate(elements,cap-elements);
}
}
44:知識點1:const_cast<type_id> (expression)
該運算符用來修改類型的const或volatile屬性。除了const 或volatile修飾之外, type_id和expression的類型是一樣的。
一、常量指針被轉化成非常量的指針,并且仍然指向原來的對象;(本例中用到)
二、常量引用被轉換成非常量的引用,并且仍然指向原來的對象;
三、const_cast一般用于修改底指針。如const char *p形式。
知識點2:别怕煩,動手寫!
#ifndef STRVEC_H
#define STRVEC_H
#include <string>
#include <algorithm>
#include <memory>
using namespace std;
class String
{
public:
String();//預設構造函數
String(const char*s)//接受c風格字元串參數的構造函數,s為指向字元串的指針(首位置)
{
auto s1 = const_cast<char*>(s);//轉化為非常量的指針
while(*s1)
{
++s1;//使其指向最後一個位置的尾部
}
alloc_n_copy(s,s1);//進行拷貝
}
String(const String&);//拷貝構造函數
String& operator=(const String&);//拷貝指派運算符
~String()//析構函數
{
free();
}
void free()//釋放記憶體
{
if (elements)
{
for_each(elements,end,[this](char &rhs){alloc.destroy(&rhs);});
alloc.deallocate(elements,end-elements);
}
}
private:
allocator<char> alloc;//配置設定記憶體的方法
char *elements;//首尾指針
char *end;
std::pair<char*, char*> alloc_n_copy(const char*a, const char*b)//拷貝指派函數
{
auto s1 = alloc.allocate(b-a);//allocate參數為配置設定記憶體的大小
auto s2 = uninitialized_copy(a,b,s1);//拷貝指派,将a到b之間的元素拷貝至s1,傳回的是最後一個構造元素之後的位置
return make_pair(s1,s2);//傳回首尾指針
}
void range_initializer(const char*c, const char*d)//初始化
{
auto p = alloc_n_copy(d,c);//拷貝并初始化新的string
elements = p.first;
end = p.second;//将新的string的首尾指針指派
}
};
#endif STRVEC_H
45:知識點1:新标準的特性:可以移動而非拷貝對象,可直接調用std::move(),移動而非拷貝會大幅度的提高性能:不必要進行舊記憶體到新記憶體的拷貝,當對象很大時省下很多的記憶體,另一個原因是像IO類和unique_ptr這樣的類不可以進行拷貝,但是我們可以使用移動來共享其中的資源
知識點2:右值引用:必須綁定到右值的引用通過兩個取址符号&&來獲得右值引用:隻綁定到一個将要銷毀的對象,是以,我們可以自由的将右值引用的資源“移動”到另一個對象中
知識點3:左值和右值的差別:P121頁
知識點4:一個右值引用本質上也隻是一個對象的另外一個名字而已。對于正常的引用:稱之為左值引用,我們不能将其綁定到所要轉換的表達式,而右值引用可以綁定,見下例:
int a = 42;
int &p1 = a;//正确,是a的引用
int &&p2 = a;//錯誤,不能将一個右值綁定到一個左值上
int &p3 = a *42;//錯誤,a*42為右值,&iii為左值引用
const int &p4 = a*42;//正确,可以将一個右值綁定到一個const的引用上
int &&p5 = a*42;//正确,右值綁定
知識點5:由上例可知,左值持久,右值短暫,左值具有持久的狀态,右值要麼是字面值常量,要麼是在表達式求值的過程中建立的臨時對象(沒有其他使用者進行綁定且要被銷毀):使用右值引用的代碼可以自由的接管所引用的對象的資源
知識點6:變量是左值
int &&p6 = 42;//正确,字面值是右值
int &&p7 = p6;//錯誤,不能将一個右值引用直接綁定到一個變量上,即使這個變量是右值引用類型
答案見知識點
46:
(a):f()為函數的傳回值,臨時值,屬于右值,&&
(b):vi[0]為變量,屬于左值,&
(c):r1為變量,屬于左值,&
(d):右側為表達式,屬于右值,&&
47:知識點:可以通過一個名為move()的函數來獲得綁定到左值上的右值引用,頭檔案為utility,move告訴編譯器,我有一個左值,但是我想像一個右值一樣處理它:除了指派或者銷毀,不能再使用它,使用move時應為std::move
String(const String& rhs)//拷貝構造函數
{
range_initializer(rhs.elements, rhs.end);
cout << "拷貝構造函數" << endl;
}
String& operator=(const String& rhs)//拷貝指派運算符
{
auto newstr = alloc_n_copy(rhs.elements, rhs.end);
free();
elements = newstr.first;
end = newstr.second;
cout << "拷貝指派運算符" << endl;
return *this;
}
48:見47
#include <iostream>
#include <vector>
#include "StrVec.h"
using namespace std;
int main(int argc, char**argv)
{
vector<String> vec;
String s1("hello");
String s2 = s1;
vec.push_back(s1);
vec.push_back(s2);
return 0;
}
49:知識點1:類的移動構造函數:參數為該類類型的右值引用,任何額外參數須有預設參數,一旦資源被移動,源對象對移動之後的資源已經不再有控制權,最後源對象會被銷毀。注意,移動構造函數是“竊取”資源,并不會配置設定資源。
知識點2:移動操作不會報出任何異常,因為其不配置設定資源,是以我們需要告知标準庫,以友善一起處理時多做一些額外的工作——加上noexpect(C++11新标準),在類的聲明和定義是都需要指出noexpect
知識點3:不抛出異常的移動構造函數和移動指派運算符必須标記為noexpect——解釋見書本P474頁
知識點4:移動後的源對象可能會在移動後被銷毀,是以必須進入可析構的狀态——将源對象的指針成員置為nullptr來實作
知識點5:當一個類為其自身定義了拷貝構造函數和拷貝指派運算符或者析構函數,且其所有資料成員皆可移動構造時,那麼編譯器将不會再為該類合成移動構造函數和移動指派運算符
知識點6:移動右值、拷貝左值、沒有移動構造函數時,右值也會被拷貝
知識點7:所有五個拷貝控制成員應該在類中一起出現!
String& String::operator=(String&& rhs) NOEXCEPT
{
if (this != &rhs) {
free();
elements = rhs.elements;
end = rhs.end;
rhs.elements = rhs.end = nullptr;//将源對象置為可析構的狀态
}
return *this;
}
String::String(String&& s) NOEXCEPT : elements(s.elements), end(s.end)
{
s.elements = s.end = nullptr;//将源對象置為可析構的狀态
}
50:
String baz()
{
String ret("world");
return ret; // 傳回值會避免拷貝
}
String s5 = baz(); // 右值會避免拷貝