C++Primer 第13章 拷贝控制

13.1 拷贝、赋值与销毁

13.1 拷贝构造函数是什么?什么时候使用它?

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则次构造函数是拷贝构造函数。

  1. 一个对象作为函数参数,以值传递的方式传入函数体;
  2. 一个对象作为函数返回值,以值传递的方式从函数返回;
  3. 一个对象用于给另外一个对象进行初始化(常称为赋值初始化);
  4. 用花括号列表初始化一个数组中的元素或一个聚合类成员。

13.2 解释为什么下面的声明是非法的:

Sales_data::Sales_data(Sales_data rhs);
// 永远也不会调用成功,为了调用拷贝构造函数,我们需要拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造函数,如此无限循环。

13.3 当我们拷贝一个StrBlob时,会发生什么?拷贝一个StrBlobPtr呢?

拷贝StrBlob时shared_ptr+1,拷贝StrBlobPtr不会。

13.4 假定 Point 是一个类类型,它有一个public的拷贝构造函数,指出下面程序片段中哪些地方使用了拷贝构造函数:

Point global;
Point foo_bar(Point arg) //1 函数参数
{
    Point local = arg, *heap = new Point(global); //2,3 赋值初始化
    *heap = local;
    Point pa[ 4 ] = { local, *heap }; //4,5 列表初始化
    return *heap; //6 返回值
}

13.5 给定下面的类框架,编写一个拷贝构造函数,拷贝所有成员。你的构造函数应该动态分配一个新的string,并将对象拷贝到ps所指向的位置,而不是拷贝ps本身:

#ifndef HASPTR_H
#define HASPTR_H

#include <iostream>
#include <string>

using namespace std;

class HasPtr
{
public:
    HasPtr(const string& s = string()) : ps(new string(s)), i(0) { }
    HasPtr(const HasPtr& hp) : ps(new string(*hp.ps)), i(hp.i) { }
    HasPtr& operator=(const HasPtr& hp)
    {
        auto newp = new string(*hp.ps);
        delete ps;
        ps = newp;
        i = hp.i;
        return *this;   
    }

    ~HasPtr() 
    {
        delete ps;
    }

private:
    string* ps;
    int i;
};

#endif

13.1.1 拷贝构造函数

13.1.2 拷贝赋值运算符

13.6 拷贝赋值运算符是什么?什么时候使用它?合成拷贝赋值运算符完成什么工作?什么时候会生成合成拷贝赋值运算符?

拷贝赋值运算符是一个名为operator=的函数,它接受与类相同类型的参数;
当赋值发生时使用该运算符;
将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,对于数组类型的成员,逐个赋值数组元素,合成拷贝赋值运算符返回一个指向其左侧运算对象的引用;
如果一个类未定义自己的拷贝赋值运算符。

13.7 当我们将一个 StrBlob 赋值给另一个 StrBlob 时,会发生什么?赋值 StrBlobPtr 呢?

同13.3一样,赋值StrBlob时shared_ptr+1,赋值StrBlobPtr不会。

13.8 为13.1.1节练习13.5中的 HasPtr 类编写赋值运算符。类似拷贝构造函数,你的赋值运算符应该将对象拷贝到ps指向的位置。

    HasPtr& operator=(const HasPtr& hp)
    {
        ps = new string(*hp.ps);
        i = hp.i;
        cout << *ps << " " << i << endl;
        return *this;        
    }

13.1.3 析构函数

13.9 析构函数是什么?合成析构函数完成什么工作?什么时候会生成合成析构函数?

析构函数是类的一个成员函数,名字由波浪号接类名构成,它没有返回值,也不接受参数,用于释放对象所使用的资源,并销毁对象的非static数据成员;
类似拷贝构造函数和拷贝赋值运算符,对于某些类,和合成析构函数被用来阻止该类型的对象被销毁,如果不是这种情况,合成析构函数的函数体就为空;
当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。

13.10 当一个 StrBlob 对象销毁时会发生什么?一个 StrBlobPtr 对象销毁时呢?

StrBlob对象销毁时,shared_ptr-1,直到为0时,动态对象将销毁;StrBlobPtr对象销毁时,其指向的动态对象不会被销毁。

13.11 为前面练习中的 HasPtr 类添加一个析构函数。

~HasPtr() {delete ps;}

13.12 在下面的代码片段中会发生几次析构函数调用?

bool fcn(const Sales_data *trans, Sales_data accum)
{
	Sales_data item1(*trans), item2(accum);
	return item1.isbn() != item2.isbn();
}
// 离开后accum、item1和item2销毁。

13.13 理解拷贝控制成员和构造函数的一个好方法的定义一个简单的类,为该类定义这些成员,每个成员都打印出自己的名字:
给 X 添加拷贝赋值运算符和析构函数,并编写一个程序以不同的方式使用 X 的对象:将它们作为非引用参数传递;动态分配它们;将它们存放于容器中;诸如此类。观察程序的输出,直到你确认理解了什么时候会使用拷贝控制成员,以及为什么会使用它们。当你观察程序输出时,记住编译器可以略过对拷贝构造函数的调用。

#include <iostream>
using namespace std;

struct X
{
    X() {cout << "X()" << endl;}
    X(const X&) {cout << "X(const X&)" << endl;}
    X operator=(const X&) {cout << "X operator=(const &X)" << endl;}
    ~X() {cout << "~X()" << endl;}
};

// void func(const X) {cout << "void func(const X)" << endl;}
void func(const X&) {cout << "void func(const X&)" << endl;}

int main()
{
    X x1;
    // X x2 = x1;
    // X x2;
    // x1 = x2;

    func(x1);

    return 0;
}

13.1.4 三/五法则

13.14 假定 numbered 是一个类,它有一个默认构造函数,能为每个对象生成一个唯一的序号,保存在名为 mysn 的数据成员中。假定 numbered 使用合成的拷贝控制成员,并给定如下函数:则下面代码输出什么内容?

void f (numbered s) { cout << s.mysn < endl; }
numbered a, b = a, c = b;
f(a); f(b); f(c);

输出同一个mysn。

13.15 假定numbered 定义了一个拷贝构造函数,能生成一个新的序列号。这会改变上一题中调用的输出结果吗?如果会改变,为什么?新的输出结果是什么?

会,在拷贝初始化时会调用拷贝构造函数,能生成一个新的序号,但是,调用f函数时又生成一个新的序号,所以,新的输出结果会输出不同的mysn,但不是a、b、c的mysn

13.16 如果 f 中的参数是 const numbered&,将会怎样?这会改变输出结果吗?如果会改变,为什么?新的输出结果是什么?

会,在拷贝初始化时会调用拷贝构造函数,能生成一个新的序号,所以,新的输出结果会输出不同的mysn,是a、b、c的mysn。

13.1.5 使用=default

13.1.6 阻止拷贝

13.18 定义一个 Employee 类,它包含雇员的姓名和唯一的雇员证号。为这个类定义默认构造函数,以及接受一个表示雇员姓名的 string 的构造函数。每个构造函数应该通过递增一个 static 数据成员来生成一个唯一的证号。

#include <iostream>
#include <string>
using namespace std;

class Employee 
{
public:
    friend void print(const Employee& employee);
    Employee(){ID = n++; name = "";}
    Employee(const string& name) : name(name){ID = n++;}

private:
    string name;
    int ID;
    static int n;
};

int Employee::n = 0;

void print(const Employee& employee)
{
    cout << employee.name << endl;
    cout << "ID: " << employee.ID << endl;
}

int main()
{
    Employee employee1, employee2("aaa");
    print(employee1);
    print(employee2);

    return 0;
}

13.19 你的 Employee 类需要定义它自己的拷贝控制成员吗?如果需要,为什么?如果不需要,为什么?实现你认为 Employee 需要的拷贝控制成员。

不需要,员工在现实中不能复制。

Employee& operator=(const Employee&) = delete;

13.20 解释当我们拷贝、赋值或销毁 TextQuery 和 QueryResult 类对象时会发生什么?

成员对象会被复制。

13.21 你认为 TextQuery 和 QueryResult 类需要定义它们自己版本的拷贝控制成员吗?如果需要,为什么?实现你认为这两个类需要的拷贝控制操作。

不需要,合成拷贝控制成员已满足需求。

13.2 拷贝控制和资源管理

13.22 假定我们希望 HasPtr 的行为像一个值。即,对于对象所指向的 string 成员,每个对象都有一份自己的拷贝。我们将在下一节介绍拷贝控制成员的定义。但是,你已经学习了定义这些成员所需的所有知识。在继续学习下一节之前,为 HasPtr 编写拷贝构造函数和拷贝赋值运算符。

见13.11。

13.2.1 行为像值的类

13.23 比较上一节练习中你编写的拷贝控制成员和这一节中的代码。确定你理解了你的代码和我们的代码之间的差异。

13.24 如果本节的 HasPtr 版本未定义析构函数,将会发生什么?如果未定义拷贝构造函数,将会发生什么?

如果未定义析构函数,将会发生内存泄漏,动态内存得不到释放,直到没有内存可以申请;如果未定义拷贝构造函数,指针将被复制,可能会多次释放同一个内存。

12.25 假定希望定义 StrBlob 的类值版本,而且希望继续使用 shared_ptr,这样我们的 StrBlobPtr 类就仍能使用指向vector的 weak_ptr 了。你修改后的类将需要一个拷贝的构造函数和一个拷贝赋值运算符,但不需要析构函数。解释拷贝构造函数和拷贝赋值运算符必须要做什么。解释为什么不需要析构函数。

拷贝构造函数和拷贝赋值运算符需要使用值新建一个shared_ptr,当类销毁时,shared_ptr计数减1,当计数为0时,其指向的对象会自动销毁。

12.26 对上一题中描述的 strBlob 类,编写你自己的版本。

#ifndef STRBLOB_H
#define STRBLOB_H

#include <memory>
#include <vector>
#include <string>
#include <initializer_list>

using namespace std;

class StrBlobPtr;

class StrBlob
{
public:
    friend class StrBlobPtr;

    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);
    StrBlob(const StrBlob&);
    StrBlob operator=(const StrBlob&);

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const string &t) { data->push_back(t); }

    void pop_back();
    string &front();
    string &back();

    string &front() const;
    string &back() const;
    StrBlobPtr begin();
    StrBlobPtr end();

private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string &msg) const;
};

// 对于访问一个不存在元素的尝试,StrBlobPtr抛出一个异常
class StrBlobPtr
{
public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}

    string &deref() const; // 解引用
    StrBlobPtr &incr();    // 前置递增

    bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }

private:
    shared_ptr<vector<string>> check(size_t, const string &) const;
    weak_ptr<vector<string>> wptr;
    size_t curr;
};

StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
StrBlob::StrBlob(const StrBlob& other) {data = make_shared<vector<string>>(*other.data); }
StrBlob StrBlob::operator=(const StrBlob& other) 
{
    data = make_shared<vector<string>>(*other.data);
    return *this;
}

void StrBlob::check(size_type i, const string &msg) const
{
    if (i >= data->size())
        throw out_of_range(msg);
}

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

string &StrBlob::front()
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back()
{
    check(0, "back on empty StrBlob");
    return data->back();
}

string &StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back() const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();
    if (!ret)
        throw runtime_error("unbound StrBlobStr");
    if (i >= ret->size())
        throw out_of_range(msg);
    return ret;
}

string& StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::incr()
{
    auto p = check(curr, "increment past end of StrBlobPtr");
    ++curr;
    return *this;
}

StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end()
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

#endif

13.2.2 定义行为像指针的类

13.27 定义你自己的使用引用计数版本的 HasPtr。

#include <iostream>
#include <string>

using namespace std;

class HasPtr
{
public:
    HasPtr(const string& s = string()) : ps(new string(s)), i(0),  use(new size_t(1)){ }
    HasPtr(const HasPtr& hp) : ps(hp.ps), i(hp.i), use(hp.use){++*use;}
    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() 
    {
        if (--*use == 0)
        {
            delete ps;
            delete use;
        }
    }

private:
    string* ps;
    int i;
    size_t *use;
};
int main()
{
    HasPtr has;
    has = HasPtr("hello");
    HasPtr has1 = has;
    HasPtr has2;
    has2 = has;

    return 0;
}

13.28 给定下面的类,为其实现一个默认构造函数和必要的拷贝控制成员。

#include <iostream>

using namespace std;

class TreeNode
{
public:
    TreeNode() : value(string()), count(new int(1)), left(nullptr), right(nullptr){}
    TreeNode(const TreeNode& rhs) : value(rhs.value), count(rhs.count), left(rhs.left), right(rhs.right) {++*count;};
    TreeNode operator=(const TreeNode& rhs);

    ~TreeNode()
    {
        if((--*count) == 0)
        {
            // cout << "~TreeNode 执行“ "<< endl;
            // cout << *count << endl;
            delete count;
            delete left;
            delete right;
        }
    }

private:
    string value;
    int *count;
    TreeNode *left;
    TreeNode *right;
};

class BinStrTree
{
public:
    BinStrTree() : root(new TreeNode()) {}
    BinStrTree(const BinStrTree& rhs) : root(new TreeNode(*rhs.root)){}
    BinStrTree& operator=(const BinStrTree& rhs);

    ~BinStrTree(){delete root;}
private:
    TreeNode *root;
};

TreeNode TreeNode::operator=(const TreeNode& rhs)
{
    ++*rhs.count;
    if (*count == 0)
    {
        delete left;
        delete right;
        delete count;
    }
    value = rhs.value;
    count = rhs.count;
    left = rhs.left;
    right = rhs.right;

    return *this;
}

BinStrTree& BinStrTree::operator=(const BinStrTree& rhs)
{
    if (this == &rhs)
        return *this;

    delete root;
    root = new TreeNode(*rhs.root);

    return *this;
}

int main()
{
    TreeNode tree1;
    TreeNode tree2(tree1);
    tree1 = tree2;

    BinStrTree b1;
    BinStrTree b2(b1);
    b1 = b2;

    return 0;
}

13.3 交换操作

13.29 解释 swap(HasPtr&, HasPtr&)中对 swap 的调用不会导致递归循环。

函数的参数不一样,调用的函数不一样。

13.30 为你的类值版本的 HasPtr 编写 swap 函数,并测试它。为你的 swap 函数添加一个打印语句,指出函数什么时候执行。

#ifndef HASPTR_H
#define HASPTR_H

#include <iostream>
#include <string>
#include <algorithm>

class HasPtr
{
    friend void swap(HasPtr&, HasPtr&);
public:
    HasPtr(const std::string& s = std::string()) : ps(new std::string(s)), i(0) { }
    HasPtr(const HasPtr& hp) : ps(new std::string(*hp.ps)), i(hp.i) { }
    HasPtr& operator=(const HasPtr& hp)
    {
        auto newp = new std::string(*hp.ps);
        delete ps;
        ps = newp;
        i = hp.i;
        return *this;   
    }

    ~HasPtr() 
    {
        delete ps;
    }

private:
    std::string* ps;
    int i;
};

inline void swap(HasPtr& lhs, HasPtr& rhs)
{
    std::cout << "lhs: " << *lhs.ps << ", rhs: " << *rhs.ps << std::endl;
    using std::swap;
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
    std::cout << "lhs: " << *lhs.ps << ", rhs: " << *rhs.ps << std::endl;
    std::cout << "swap" << std::endl;
}

#endif

13.31 为你的 HasPtr 类定义一个 < 运算符,并定义一个 HasPtr 的 vector。为这个 vector 添加一些元素,并对它执行 sort。注意何时会调用 swap。

#ifndef HASPTR_H
#define HASPTR_H

#include <iostream>
#include <string>
#include <algorithm>

class HasPtr
{
    friend void swap(HasPtr&, HasPtr&);
    friend bool operator<(const HasPtr&, const HasPtr&);
public:
    HasPtr(const std::string& s = std::string()) : ps(new std::string(s)), i(0) { }
    HasPtr(const HasPtr& hp) : ps(new std::string(*hp.ps)), i(hp.i) { }
    HasPtr& operator=(const HasPtr& hp)
    {
        auto newp = new std::string(*hp.ps);
        delete ps;
        ps = newp;
        i = hp.i;
        return *this;   
    }

    ~HasPtr() 
    {
        delete ps;
    }

private:
    std::string* ps;
    int i;
};

inline void swap(HasPtr& lhs, HasPtr& rhs)
{
    std::cout << "lhs: " << *lhs.ps << ", rhs: " << *rhs.ps << std::endl;
    using std::swap;
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
    std::cout << "lhs: " << *lhs.ps << ", rhs: " << *rhs.ps << std::endl;
    std::cout << "swap" << std::endl;
}

bool operator<(const HasPtr& lhs, const HasPtr& rhs)
{
    std::cout << " < " << std::endl;
    return *lhs.ps < *rhs.ps;
}

#endif

13.32 类指针的 HasPtr 版本会从 swap 函数收益吗?如果会,得到了什么益处?如果不是,为什么?

不会,类指针本身就是指针交换,没有内存分配。

13.4 拷贝控制示例

13.33 为什么Message的成员save和remove的参数是一个 Folder&?为什么我们不能将参数定义为 Folder 或是 const Folder?

因为需要更改Folder的值

13.34 编写本节所描述的 Message。
13.36 设计并实现对应的 Folder 类。此类应该保存一个指向 Folder 中包含 Message 的 set。
13.37 为 Message 类添加成员,实现向 folders 添加和删除一个给定的 Folder*。这两个成员类似Folder 类的 addMsg 和 remMsg 操作。

#ifndef MESSAGE_H
#define MESSAGE_H

#include <string>
#include <set>
#include <iostream>

class Folder;


class Message
{
    friend class Folder;
    friend void swap(Message& lhs, Message& rhs);
public:
    explicit Message(const std::string& str = " ") : contents(str) {}

    Message(const Message&);
    Message& operator=(const Message&);
    ~Message();

    void save(Folder&);
    void remove(Folder&);
private:
    std::string contents;
    std::set<Folder*> folders;
    void add_to_Folders(const Message&);
    void remove_from_Folders();
};

class Folder
{
public:
    Folder() : message() {}
    Folder(const Folder&);
    Folder& operator=(const Folder&);
    ~Folder();

    void addMsg(Message*);
    void remMsg(Message*);
private:
    std::set<Message*> message;
    void add_to_Messages(const Folder&);
    void remove_from_Messages();
};

void Message::save(Folder& f)
{
    folders.insert(&f);
    f.addMsg(this);
}

void Message::remove(Folder& f)
{
    folders.erase(&f);
    f.remMsg(this);
}

void Message::add_to_Folders(const Message& m)
{
    for (auto& f : m.folders)
        f->addMsg(this);
}

Message::Message(const Message& m) : contents(m.contents), folders(m.folders)
{
    add_to_Folders(m);// 将本消息添加到指向m的Folder中
}

void Message::remove_from_Folders()
{
    for (auto& f : folders)
        f->remMsg(this);
}

Message::~Message()
{
    remove_from_Folders();
}

Message& Message::operator=(const Message& m)
{
    // 先刪除指針再插入
    remove_from_Folders();
    contents = m.contents;
    folders = m.folders;
    add_to_Folders(m);
    return *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.contents, rhs.contents);
    swap(lhs.folders, rhs.folders);

    for (auto f : lhs.folders)
        f->addMsg(&lhs);
    for (auto f : rhs.folders)
        f->addMsg(&rhs);

    std::cout << "swapped " << std::endl;
}
void Folder::addMsg(Message* msg)
{
    message.insert(msg);
}

void Folder::remMsg(Message* msg)
{
    message.erase(msg);
}

void Folder::add_to_Messages(const Folder& folder)
{
    for (auto msg : folder.message)
        msg->folders.insert(this);
}

Folder::Folder(const Folder& f) 
{
    add_to_Messages(f);
}

void Folder::remove_from_Messages()
{
    for (auto msg : message)
        msg->folders.erase(this);
}

Folder::~Folder() 
{
    remove_from_Messages();
}

Folder& Folder::operator=(const Folder& rhs)
{
    remove_from_Messages();
    message = rhs.message;
    add_to_Messages(rhs);
    return *this;
}

#endif

13.35 如果Message 使用合成的拷贝控制成员,将会发生什么?

将不能正确拷贝,Message中保存的Folder信息与Folder中保存的Message信息不统一。

13.38 我们并未使用拷贝交换方式来设计 Message 的赋值运算符。你认为其原因是什么?

动态内存分配时用拷贝和交换比较好,本题中,交换是自定义的,会清除Forder中的Message再添加,因此拷贝和交换都会造成额外的消耗。

13.5 动态内存管理类

13.39 编写你自己版本的 StrVec,包括自己版本的 reserve、capacity 和 resize。
13.40 为你的 StrVec 类添加一个构造函数,它接受一个 initializer_list 参数。
13.43 重写 free 成员,用 for_each 和 lambda 来代替 for 循环 destroy 元素。你更倾向于哪种实现,为什么?

#ifndef STRVEC_H
#define STRVEC_H

#include <string>
#include <memory>
#include <initializer_list>
#include <algorithm>

class StrVec
{
public:
    StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {} // alloc进行默认初始化
    StrVec(std::initializer_list<std::string>);
    StrVec(const StrVec &);                                            // 拷贝构造函数
    StrVec &operator=(const StrVec &);                                 // 拷贝赋值运算符
    ~StrVec();                                                       // 析构函数

    void push_back(const std::string &); // 向末尾添加一个字符串

    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }
    void reserve(size_t);
    void resize(size_t);
    void resize(size_t, const std::string &);

    std::string *begin() const { return elements; }
    std::string *end() const { return first_free; }

private:
    // 三指针
    std::string *elements;   // 指向分配内存的首元素
    std::string *first_free; // 指向最后一个实际元素之后的位置
    std::string *cap;        // 指向分配的内存末尾之后的位置

    static std::allocator<std::string> alloc; // 分配元素

    // 分配内存,并拷贝一个给定范围的元素
    std::pair<std::string *, std::string *> alloc_n_copy(const std::string *, const std::string *);

    // 销毁构造的元素并释放内存
    void free();

    // 检查是否有容纳一个新元素的空间
    void chk_n_alloc()
    {
        if (size() == capacity())
            reallocate();
    }

    // 如果没有,reallocate分配更多内存
    void reallocate();
};

std::allocator<std::string> StrVec::alloc;

void StrVec::push_back(const std::string &s)
{
    chk_n_alloc();
    // 再firs_free指向的元素中构造s的副本
    alloc.construct(first_free++, s);
}

std::pair<std::string *, std::string *> StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{
    auto data = alloc.allocate(e - b);
    return {data, std::uninitialized_copy(b, e, data)};
}

void StrVec::free()
{
    if (elements)
    {
        // for (auto p = first_free; p != elements; alloc.destroy(--p))
        //    ;
        std::for_each(elements, first_free, [this](std::string &p){alloc.destroy(&p);});
        alloc.deallocate(elements, cap - elements);
    }
}

StrVec::StrVec(std::initializer_list<std::string> il)
{
    auto newdata = alloc_n_copy(il.begin(), il.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec::StrVec(const StrVec &s)
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec::~StrVec(){free();}

StrVec &StrVec::operator=(const StrVec &rhs)
{
    auto newdata = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = newdata.first;
    first_free = cap = newdata.second;
    return *this;
}

void StrVec::reallocate()
{
    auto newcapacity = size() ? size() * 2 : 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 = newdata + newcapacity;
}

void StrVec::reserve(size_t n)
{
    if (n <= capacity()) return;
    auto newdata = alloc.allocate(n);
    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 = newdata + n;
}

void StrVec::resize(size_t n)
{
    resize(n, std::string());
}

void StrVec::resize(size_t n, const std::string &s)
{
    if (n < size())
        while (n < size())
            alloc.destroy(--first_free);
    else if (n > size())
        while (n > size())
            push_back(s);
}

#endif

13.41 在 push_back 中,我们为什么在 construct 调用中使用后置递增运算?如果使用前置递增运算的话,会发生什么?

first_free为尾后指针,如果使用前置递增会空出一个位置。

13.42 在你的 TextQuery 和 QueryResult 类中用你的 StrVec 类代替vector,以此来测试你的 StrVec 类。

TextQuery.h

#ifndef TEXTQUERY_H_
#define TEXTQUERY_H_

#include <string>
// #include <vector>
#include "StrVec.h"
#include <map>
#include <fstream>
#include <sstream>
#include <set>
#include <memory>
#include <iostream>
#include <algorithm>
#include <iterator>


class QueryResult;

class TextQuery
{
public:
	using line_no = size_t;
	TextQuery(std::ifstream&);
	QueryResult query(const std::string&) const;
private:
	std::shared_ptr<StrVec> file;
	std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};

class QueryResult
{
	friend std::ostream& print(std::ostream&, const QueryResult&);
public:
	QueryResult(std::string s, std::shared_ptr<std::set<TextQuery::line_no>> p, std::shared_ptr<StrVec> f) : sought(s), lines(p), file(f) { }
private:
	std::string sought;
	std::shared_ptr<std::set<TextQuery::line_no>> lines;
	std::shared_ptr<StrVec> file;
};

TextQuery::TextQuery(std::ifstream &ifs) : file(new StrVec)
{
	std::string text;

	while(std::getline(ifs, text))
	{
		file->push_back(text);
		int n = file->size() - 1;
		std::istringstream line(text);
		std::string text;
		while(line >> text)
		{
			std::string word;
			std::copy_if(text.begin(), text.end(), std::back_inserter(word), isalpha);
			// std::cout << word << std::endl;
			auto &lines = wm[word];
			if(!lines)
				lines.reset(new std::set<line_no>);
			lines->insert(n);
		}
	}
}

QueryResult TextQuery::query(const std::string &sought) const
{
	static std::shared_ptr<std::set<TextQuery::line_no>> nodata(new std::set<TextQuery::line_no>);
	auto loc = wm.find(sought);
	if(loc == wm.end())
		return QueryResult(sought, nodata, file);
	else
		return QueryResult(sought, loc->second, file);
	// QueryResult QR;
	// auto count = word_line.count(s);
	// QR.count = count;
	// auto iter = word_line.find(s);

	// while(count)
	// {
	// 	QR.line_num.insert(iter->second);
	// 	++iter;
	// 	--count;
	// }

	// return QR;
	// // for(auto iter = word_line.lower_bound(s), end = word_line.upper_bound(s); iter != end; ++iter)
	// // {
	// // 	line_num.insert(iter->second);
	// // }
}

std::ostream &print(std::ostream &os, const QueryResult &qr)
{
	os << qr.sought << " occurs " << qr.lines->size() << " " /*<< make_plural(qr.lines->size(), "time", "s")*/ << std::endl;
	for(auto num : *qr.lines)
		os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << std::endl;
	return os;
}

#endif

13.44 编写标准库 string 类的简化版本,命名为 String。你的类应该至少有一个默认构造函数和一个接受 C 风格字符串指针参数的构造函数。使用 allocator 为你的 String类分配所需内存。

#ifndef STRING_H
#define STRING_H

#include <memory>
#include <string.h>

class String
{
public:
    String() : elements(nullptr), first_free(nullptr), cap(nullptr) {}
    String(const String&);
    String(const char*);
    String &operator=(const String&);
    ~String();
    void push_back(const char&);

    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 std::allocator<char> alloc;
    char *elements;
    char *first_free;
    char *cap;

    void chk_n_alloc()
        {if (size() == capacity()) reallocate(); }
    void reallocate();
    std::pair<char*, char*> alloc_n_copy(const char*, const char*);
    void free();
};

std::allocator<char> String::alloc;

String::String(const String& s)
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

String::String(const char* s)
{
    auto newdata = alloc_n_copy(s, s + strlen(s));
    elements = newdata.first;
    first_free = cap = newdata.second;
}

String &String::operator=(const String& s)
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    free();
    elements = newdata.first;
    first_free = cap = newdata.second;
    return *this;
}

String::~String()
{
    free();
}

void String::push_back(const char& c)
{
    chk_n_alloc();
    alloc.construct(first_free++, c);
}

std::pair<char*, char*> String::alloc_n_copy(const char* b, const char* e)
{
    auto data = alloc.allocate(e - b);
    return {data, std::uninitialized_copy(b, e, data)};
}

void String::reallocate()
{
    auto newcapacity = size() ? size() * 2 : 1;
    auto newdata = alloc.allocate(newcapacity);
    auto dest = newdata;
    auto elem = elements;
    for (size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, *elem++);
    free();
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

void String::free()
{
    for (auto p = elements; p!= first_free; ++p)
        alloc.destroy(p);
    alloc.deallocate(elements, capacity());
}

#endif

13.6 对象移动

13.6.1 右值引用

13.45 解释左值引用和右值引用的区别?

左值引用是绑定到左值上的引用,左值持久;
右值引用是绑定到右值上的引用,右值短暂,右值引用可以绑定到要求转换的表达式、字面值常量或是返回右值的表达式上。

13.46 什么类型的引用可以绑定到下面的初始化器上?

int f();
vector<int> vi(100);
int&& r1 = f();
int& r2 = vi[0];
int& r3 = r1;
int&& r4 = vi[0] * f();

13.47 对你在练习13.44中定义的 String类,为它的拷贝构造函数和拷贝赋值运算符添加一条语句,在每次函数执行时打印一条信息。
13.48 定义一个vector 并在其上多次调用 push_back。运行你的程序,并观察 String 被拷贝了多少次。

#ifndef STRING_H
#define STRING_H

#include <memory>
#include <string.h>
#include <iostream>

class String
{
public:
    String() : elements(nullptr), first_free(nullptr), cap(nullptr) {}
    String(const String&);
    String(const char*);
    String &operator=(const String&);
    ~String();
    void push_back(const char&);

    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 std::allocator<char> alloc;
    char *elements;
    char *first_free;
    char *cap;

    void chk_n_alloc()
        {if (size() == capacity()) reallocate(); }
    void reallocate();
    std::pair<char*, char*> alloc_n_copy(const char*, const char*);
    void free();
};

std::allocator<char> String::alloc;

String::String(const String& s)
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
    std::cout << "String::String(const String&)" << std::endl;
    
}

String::String(const char* s)
{
    auto newdata = alloc_n_copy(s, s + strlen(s));
    elements = newdata.first;
    first_free = cap = newdata.second;
}

String &String::operator=(const String& s)
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    free();
    elements = newdata.first;
    first_free = cap = newdata.second;
    std::cout << "String &String::operator=(const String&)" << std::endl;
    return *this;
}

String::~String()
{
    free();
}

void String::push_back(const char& c)
{
    chk_n_alloc();
    alloc.construct(first_free++, c);
}

std::pair<char*, char*> String::alloc_n_copy(const char* b, const char* e)
{
    auto data = alloc.allocate(e - b);
    return {data, std::uninitialized_copy(b, e, data)};
}

void String::reallocate()
{
    auto newcapacity = size() ? size() * 2 : 1;
    auto newdata = alloc.allocate(newcapacity);
    auto dest = newdata;
    auto elem = elements;
    for (size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, *elem++);
    free();
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

void String::free()
{
    for (auto p = elements; p!= first_free; ++p)
        alloc.destroy(p);
    alloc.deallocate(elements, capacity());
}

#endif
#include <vector>
#include "String.h"

int main()
{
    std::vector<String> vs;
    vs.push_back("Hello");
    vs.push_back("World");

    return 0;
}

显示被拷贝了3次(只执行了两次拷贝,vector有扩容)

13.6.2 移动构造函数和移动赋值运算符

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

StrVec.h

#ifndef STRVEC_H
#define STRVEC_H

#include <string>
#include <memory>
#include <initializer_list>
#include <algorithm>

class StrVec
{
public:
    StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {} // alloc进行默认初始化
    StrVec(std::initializer_list<std::string>);
    StrVec(const StrVec &);// 拷贝构造函数
    StrVec(StrVec &&) noexcept;                                            
    StrVec &operator=(const StrVec &);                                 // 拷贝赋值运算符
    ~StrVec();                                                       // 析构函数

    void push_back(const std::string &); // 向末尾添加一个字符串

    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }
    void reserve(size_t);
    void resize(size_t);
    void resize(size_t, const std::string &);

    std::string *begin() const { return elements; }
    std::string *end() const { return first_free; }

private:
    // 三指针
    std::string *elements;   // 指向分配内存的首元素
    std::string *first_free; // 指向最后一个实际元素之后的位置
    std::string *cap;        // 指向分配的内存末尾之后的位置

    static std::allocator<std::string> alloc; // 分配元素

    // 分配内存,并拷贝一个给定范围的元素
    std::pair<std::string *, std::string *> alloc_n_copy(const std::string *, const std::string *);

    // 销毁构造的元素并释放内存
    void free();

    // 检查是否有容纳一个新元素的空间
    void chk_n_alloc()
    {
        if (size() == capacity())
            reallocate();
    }

    // 如果没有,reallocate分配更多内存
    void reallocate();
};

std::allocator<std::string> StrVec::alloc;

void StrVec::push_back(const std::string &s)
{
    chk_n_alloc();
    // 再firs_free指向的元素中构造s的副本
    alloc.construct(first_free++, s);
}

std::pair<std::string *, std::string *> StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{
    auto data = alloc.allocate(e - b);
    return {data, std::uninitialized_copy(b, e, data)};
}

void StrVec::free()
{
    if (elements)
    {
        // for (auto p = first_free; p != elements; alloc.destroy(--p))
        //    ;
        std::for_each(elements, first_free, [this](std::string &p){alloc.destroy(&p);});
        alloc.deallocate(elements, cap - elements);
    }
}

StrVec::StrVec(std::initializer_list<std::string> il)
{
    auto newdata = alloc_n_copy(il.begin(), il.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

StrVec::StrVec(const StrVec &s)
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
}

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::~StrVec(){free();}

StrVec &StrVec::operator=(const StrVec &rhs)
{
    auto newdata = alloc_n_copy(rhs.begin(), rhs.end());
    free();
    elements = newdata.first;
    first_free = cap = newdata.second;
    return *this;
}

void StrVec::reallocate()
{
    auto newcapacity = size() ? size() * 2 : 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 = newdata + newcapacity;
}

void StrVec::reserve(size_t n)
{
    if (n <= capacity()) return;
    auto newdata = alloc.allocate(n);
    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 = newdata + n;
}

void StrVec::resize(size_t n)
{
    resize(n, std::string());
}

void StrVec::resize(size_t n, const std::string &s)
{
    if (n < size())
        while (n < size())
            alloc.destroy(--first_free);
    else if (n > size())
        while (n > size())
            push_back(s);
}

#endif

String

#ifndef STRING_H
#define STRING_H

#include <memory>
#include <string.h>
#include <iostream>

class String
{
public:
    String() : elements(nullptr), first_free(nullptr), cap(nullptr) {}
    String(const String&);
    String(String&&) noexcept;
    String(const char*);
    String &operator=(String&&);
    String &operator=(const String&);
    ~String();
    void push_back(const char&);

    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 std::allocator<char> alloc;
    char *elements;
    char *first_free;
    char *cap;

    void chk_n_alloc()
        {if (size() == capacity()) reallocate(); }
    void reallocate();
    std::pair<char*, char*> alloc_n_copy(const char*, const char*);
    void free();
};

std::allocator<char> String::alloc;

String::String(const String& s)
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    elements = newdata.first;
    first_free = cap = newdata.second;
    std::cout << "String::String(const String&)" << std::endl;
}

String::String(String&& s) noexcept : elements(s.elements), first_free(s.first_free), cap(s.cap)
{
    s.elements = s.first_free = s.cap = nullptr;
    std::cout << "String::String(String&&)" << std::endl;
}


String::String(const char* s)
{
    auto newdata = alloc_n_copy(s, s + strlen(s));
    elements = newdata.first;
    first_free = cap = newdata.second;
}

String &String::operator=(String&& 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::operator=(const String& s)
{
    auto newdata = alloc_n_copy(s.begin(), s.end());
    free();
    elements = newdata.first;
    first_free = cap = newdata.second;
    std::cout << "String &String::operator=(const String&)" << std::endl;
    return *this;
}

String::~String()
{
    free();
}

void String::push_back(const char& c)
{
    chk_n_alloc();
    alloc.construct(first_free++, c);
}

std::pair<char*, char*> String::alloc_n_copy(const char* b, const char* e)
{
    auto data = alloc.allocate(e - b);
    return {data, std::uninitialized_copy(b, e, data)};
}

void String::reallocate()
{
    auto newcapacity = size() ? size() * 2 : 1;
    auto newdata = alloc.allocate(newcapacity);
    auto dest = newdata;
    auto elem = elements;
    for (size_t i = 0; i != size(); ++i)
        alloc.construct(dest++, *elem++);
    free();
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

void String::free()
{
    for (auto p = elements; p!= first_free; ++p)
        alloc.destroy(p);
    alloc.deallocate(elements, capacity());
}

#endif

Message

#ifndef MESSAGE_H_
#define MESSAGE_H_

#include <string>
#include <set>

class Folder;

class Message
{
	friend class Folder;
	friend void swap(Message&, Message&);
public:
	explicit Message(const std::string &str = "") : contents(str) { };
	Message(const Message&);
    Message(Message&&);
    Message& operator=(Message&&);
	Message& operator=(const Message&);
	~Message();
	void save(Folder&);
	void remove(Folder&);
private:
	std::string contents;
	std::set<Folder*> folders;
	void add_to_Folders(const Message&);
    void move_Folders(Message*);
	void remove_from_Folders();
	void addFldr(Folder *f) { folders.insert(f); }
	void remFldr(Folder *f) { folders.erase(f); }
};

class Folder
{
	friend void swap(Folder&, Folder&);
	friend class Message;
public:
	Folder() = default;
	Folder(const Folder &);
	Folder& operator=(const Folder&);
	~Folder();
private:
	std::set<Message*> msgs;
	void add_to_Message(const Folder&);
	void remove_from_Message();
	void addMsg(Message *m) { msgs.insert(m); }
	void remMsg(Message *m) { msgs.erase(m); }
};

void Message::save(Folder &f)
{
	folders.insert(&f);
	f.addMsg(this);
}

void Message::remove(Folder &f)
{
	folders.erase(&f);
	f.remMsg(this);
}

void Message::add_to_Folders(const Message &m)
{
	for(auto f : m.folders)
		f->addMsg(this);
}

Message::Message(const Message & m) : contents(m.contents), folders(m.folders)
{
	add_to_Folders(m);
}

void Message::remove_from_Folders()
{
	for(auto f : folders)
		f->remMsg(this);
}

Message::~Message()
{
	remove_from_Folders();
}

Message& Message::operator=(const Message &rhs)
{
	remove_from_Folders();
	contents = rhs.contents;
	folders = rhs.folders;
	add_to_Folders(rhs);
	return *this;
}

void Message::move_Folders(Message *m)
{
    folders = std::move(m->folders);
    for (auto f : folders)
    {
        f->remMsg(m);
        f->addMsg(this);
    }
    m->folders.clear();
}

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.contents);
        move_Folders(&rhs);
    }
    return *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);
// }

void Folder::add_to_Message(const Folder &f)
{
	for(auto m : f.msgs)
		m->addFldr(this);
}

Folder::Folder(const Folder &f) : msgs(f.msgs)
{
	add_to_Message(f);
}

void Folder::remove_from_Message()
{
	for(auto m : msgs)
		m->remFldr(this);
}

Folder::~Folder()
{
	remove_from_Message();
}

Folder &Folder::operator=(const Folder &rhs)
{
	remove_from_Message();
	msgs = rhs.msgs;
	add_to_Message(rhs);
	return *this;
}

#endif

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

在这里插入图片描述

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

此时使用的是移动操作,而不是拷贝赋值操作,所以可以正常工作。

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

hp = hp2;调用拷贝赋值运算符时会进行拷贝初始化,左值被拷贝,因此会调用拷贝构造函数;
hp = std::move(hp2);经过move后,是右值,移动构造函数为精确匹配。

13.6.3 右值引用和成员函数

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

#ifndef STRBLOB_H
#define STRBLOB_H

#include <memory>
#include <vector>
#include <string>
#include <initializer_list>

using namespace std;

class StrBlobPtr;

class StrBlob
{
public:
    friend class StrBlobPtr;

    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);
    StrBlob(const StrBlob&);
    StrBlob operator=(const StrBlob&);

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const string &t) & { data->push_back(t); }
    void push_back(string &&t) && { data->push_back(t); }

    void pop_back();
    string &front();
    string &back();

    string &front() const;
    string &back() const;
    StrBlobPtr begin();
    StrBlobPtr end();

private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string &msg) const;
};

// 对于访问一个不存在元素的尝试,StrBlobPtr抛出一个异常
class StrBlobPtr
{
public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}

    string &deref() const; // 解引用
    StrBlobPtr &incr();    // 前置递增

    bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }

private:
    shared_ptr<vector<string>> check(size_t, const string &) const;
    weak_ptr<vector<string>> wptr;
    size_t curr;
};

StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}
StrBlob::StrBlob(const StrBlob& other) {data = make_shared<vector<string>>(*other.data); }
StrBlob StrBlob::operator=(const StrBlob& other) 
{
    data = make_shared<vector<string>>(*other.data);
    return *this;
}

void StrBlob::check(size_type i, const string &msg) const
{
    if (i >= data->size())
        throw out_of_range(msg);
}

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

string &StrBlob::front()
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back()
{
    check(0, "back on empty StrBlob");
    return data->back();
}

string &StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back() const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();
    if (!ret)
        throw runtime_error("unbound StrBlobStr");
    if (i >= ret->size())
        throw out_of_range(msg);
    return ret;
}

string& StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::incr()
{
    auto p = check(curr, "increment past end of StrBlobPtr");
    ++curr;
    return *this;
}

StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end()
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

#endif

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

Foo Foo::sorted() const & {
	Foo ret(*this);
	return ret.sorted();
}

ret是一个左值,使用ret.sorted()时,调用的是左值sorted,还是本函数,这会导致进入死循环,堆栈溢出。

13.57 如果 sorted定义如下,会发生什么:

Foo Foo::sorted() const & { return Foo(*this).sorted(); }

可以使用,Foo(*this)是右值,会调用右值版本sorted。

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

#include <iostream>
#include <vector>
#include <algorithm>

class Foo
{
public:
    Foo sort() &&;
    Foo sort() const &;
private:
    std::vector<int> data;
};

int main()
{
    Foo foo;
    foo.sort();

    return 0;
}

Foo Foo::sort() &&
{
    std::sort(data.begin(), data.end());
    return *this;
}

// Foo Foo::sort() const &
// {
//     Foo ret(*this);
//     std::cout << "Foo Foo::sort() const &" << std::endl;
//     return ret.sort();
// }

Foo Foo::sort() const &
{
    std::cout << "Foo Foo::sort() const & Foo(*this).sort()" << std::endl;
    return Foo(*this).sort();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值