第十三章 拷贝控制
13.1
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
拷贝初始化是依靠拷贝构造函数或移动构造函数。拷贝初始化发生在:
(a) 使用 = 定义变量
(b) 将一个对象作为实参传递给一个非引用类型的形参
(c) 从一个返回类型为非引用类型的函数返回一个对象
(d) 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
(e)某些类型还会对他们所分配的对象使用拷贝初始化
13.2
如果其参数不是引用类型,则调用永远不会成功。为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造函数,如此无限循环。
13.3
// added a public member function to StrBlob and StrBlobPrts
long count() {
return data.use_count(); // and wptr.use_count();
}
// test codes in main()
StrBlob str({"hello", "world"});
std::cout << "before: " << str.count() << std::endl; // 1
StrBlob str_cp(str);
std::cout << "after: " << str.count() << std::endl; // 2
ConstStrBlobPtr p(str);
std::cout << "before: " << p.count() << std::endl; // 2
ConstStrBlobPtr p_cp(p);
std::cout << "after: " << p.count() << std::endl; // 2
当拷贝一个StrBlob,shared_ptr的use_count()会增加1.
当拷贝一个StrBlobPtr, weak_ptr的use_count()不会改变.
13.4
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
#ifndef CP5_ex13_05_h
#define CP5_ex13_05_h
#include
class 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) {}
private:
std::string* ps;
int i;
};
#endif
13.6
- 拷贝赋值运算符是一个名为operator=的函数,接受一个与其所在类相同类型的参数,返回一个指向其左侧运算对象的引用。
- 赋值时用。
- 对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值,如果并非出于此目的,它会将右侧运算对象的每个非静态成员赋予左侧运算对象的对应成员。
- 如果一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符。
13.7
在这两种情况下,都会发生浅拷贝。所有指针指向同一地址。使用计数与13.3相同。
13.8
#ifndef CP5_ex13_08_h
#define CP5_ex13_08_h
#include
class 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)
{
std::string* new_ps = new std::string(*hp.ps);
delete ps;
ps = new_ps;
i = hp.i;
return *this;
}
private:
std::string* ps;
int i;
};
#endif
13.9
-析构函数释放对象使用的资源,并销毁对象的非静态数据成员。是类的一个成员函数,名字由波浪号接类名构成,它没有返回值,也不接受参数。
- 对于某些类,合成析构函数被用来组织该类型的对象被销毁,如果不是这种情况,其函数体就为空,在空析构函数体指向完毕后,成员会被自动销毁。
- 当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。
13.10
当StrBlob对象被销毁时,动态对象的引用计数将递减。如果该动态对象没有共享指针,它将被释放。
当StrBlobPter对象被销毁时,动态分配的对象将不会被释放。
13.11
#ifndef CP5_ex13_11_h
#define CP5_ex13_11_h
#include
class 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)
{
std::string* new_ps = new std::string(*hp.ps);
delete ps;
ps = new_ps;
i = hp.i;
return *this;
}
~HasPtr() { delete ps; }
private:
std::string* ps;
int i;
};
#endif
13.12
3次。item1,item2,accum.
13.13
#include
#include
#include
struct X {
X() { std::cout << "X()" << std::endl; }
X(const X&) { std::cout << "X(const X&)" << std::endl; }
X& operator=(const X&)
{
std::cout << "X& operator=(const X&)" << std::endl;
return *this;
}
~X() { std::cout << "~X()" << std::endl; }
};
void f(const X& rx, X x)
{
std::vector vec;
vec.reserve(2);
vec.push_back(rx);
vec.push_back(x);
}
int main()
{
X* px = new X;
f(*px, *px);
delete px;
return 0;
}
13.14
3个相同的数字。
#include
class numbered {
public:
numbered()
{
static int unique = 10;
mysn = unique++;
}
int mysn;
};
void f(numbered s)
{
std::cout << s.mysn << std::endl;
}
int main()
{
numbered a, b = a, c = b;
f(a);
f(b);
f(c);
}
// output
// 10
// 10
// 10
13.15
是的,输出将更改。因为我们不使用合成的拷贝构造成员,而是使用自己定义的。输出将是三个不同的数字。
#include
class numbered {
public:
numbered()
{
static int unique = 10;
mysn = unique++;
}
numbered(const numbered& n) { mysn = n.mysn + 1; }
int mysn;
};
void f(numbered s)
{
std::cout << s.mysn << std::endl;
}
int main()
{
numbered a, b = a, c = b;
f(a);
f(b);
f(c);
}
// output
// 11
// 12
// 13
13.16
是的,输出将更改。因为函数f没有进行任何拷贝运算。因此,当将每个对象传递给f时,输出是相同的。
#include
class numbered {
public:
numbered()
{
static int unique = 10;
mysn = unique++;
}
numbered(const numbered& n) { mysn = n.mysn + 1;}
int mysn;
};
void f(numbered& s)
{
std::cout << s.mysn << std::endl;
}
int main()
{
numbered a, b = a, c = b;
std::cout << a.mysn << std::endl;
std::cout << b.mysn << std::endl;
std::cout << c.mysn << std::endl;
f(a);
f(b);
f(c);
}
//output
10
11
12
10
11
12
13.17
是。
13.18
ex13_18.h
#ifndef CP5_ex13_18_h
#define CP5_ex13_18_h
#include
using std::string;
class Employee {
public:
Employee();
Employee(const string& name);
const int id() const { return id_; }
private:
string name_;
int id_;
static int s_increment;
};
#endif
ex13_18.cpp
#include "ex13_18.h"
int Employee::s_increment = 0;
Employee::Employee()
{
id_ = s_increment++;
}
Employee::Employee(const string& name)
{
id_ = s_increment++;
name_ = name;
}
13.19
不需要,在现实生活中员工不需要拷贝。
#ifndef CP5_ex13_19_h
#define CP5_ex13_19_h
#include
using std::string;
class Employee {
public:
Employee();
Employee(const string& name);
Employee(const Employee&) = delete;
Employee& operator=(const Employee&) = delete;
const int id() const { return id_; }
private:
string name_;
int id_;
static int s_increment;
};
#endif
13.20
将复制成员(智能指针和容器)。
13.21
由于合成版本满足本例的所有要求,因此不需要定义自定义版本拷贝控制成员。查看#304了解详细信息。
13.22
#ifndef CP5_ex13_11_h
#define CP5_ex13_11_h
#include
class 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 new_p = new std::string(*hp.ps);
delete ps;
ps = new_p;
i = hp.i;
return *this;
}
~HasPtr() { delete ps; }
private:
std::string* ps;
int i;
};
#endif
13.23
check13.22
13.24
没有定义析构函数,会发生内存泄露。没有拷贝构造函数,会有合成拷贝构造函数,简单拷贝指针成员,造成多个对象指向相同内存。
13.25
拷贝构造函数和拷贝赋值运算符应该为自身分配动态内存,而不是与右侧操作数共享对象。
StrBlob使用的是智能指针,可以用合成析构函数来管理,如果一个StrBlob对象离开其作用域,则会自动调用std::shared_ptr的析构函数,当use_count为0时释放动态分配的内存。
13.26
ex13_26.h
#ifndef CP5_ex13_26_h
#define CP5_ex13_26_h
#include
#include
#include
#include
#include
using std::vector;
using std::string;
class ConstStrBlobPtr;
class StrBlob {
public:
using size_type = vector::size_type;
friend class ConstStrBlobPtr;
ConstStrBlobPtr begin() const;
ConstStrBlobPtr end() const;
StrBlob() : data(std::make_shared>()) {}
StrBlob(std::initializer_list il)
: data(std::make_shared>(il))
{
}
// copy constructor
StrBlob(const StrBlob& sb)
: data(std::make_shared>(*sb.data))
{
}
// copyassignment operators
StrBlob& operator=(const StrBlob& sb);
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()
{
check(0, "pop_back on empty StrBlob");
data->pop_back();
}
std::string& front()
{
check(0, "front on empty StrBlob");
return data->front();
}
std::string& back()
{
check(0, "back on empty StrBlob");
return data->back();
}
const std::string& front() const
{
check(0, "front on empty StrBlob");
return data->front();
}
const std::string& back() const
{
check(0, "back on empty StrBlob");
return data->back();
}
private:
void check(size_type i, const string& msg) const
{
if (i >= data->size()) throw std::out_of_range(msg);
}
private:
std::shared_ptr> data;
};
class ConstStrBlobPtr {
public:
ConstStrBlobPtr() : curr(0) {}
ConstStrBlobPtr(const StrBlob& a, size_t sz = 0) : wptr(a.data), curr(sz) {} // should add const
bool operator!=(ConstStrBlobPtr& p) { return p.curr != curr; }
const string& deref() const
{ // return value should add const
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
ConstStrBlobPtr& incr()
{
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
private:
std::shared_ptr> check(size_t i, const string& msg) const
{
auto ret = wptr.lock();
if (!ret) throw std::runtime_error("unbound StrBlobPtr");
if (i >= ret->size()) throw std::out_of_range(msg);
return ret;
}
std::weak_ptr> wptr;
size_t curr;
};
#endif
ex13_26.cpp
#include "ex13_26.h"
ConstStrBlobPtr StrBlob::begin() const // should add const
{
return ConstStrBlobPtr(*this);
}
ConstStrBlobPtr StrBlob::end() const // should add const
{
return ConstStrBlobPtr(*this, data->size());
}
StrBlob& StrBlob::operator=(const StrBlob& sb)
{
data = std::make_shared>(*sb.data);
return *this;
}
13_27
#ifndef CP5_ex13_27_h
#define CP5_ex13_27_h
#include
class HasPtr {
public:
HasPtr(const std::string& s = std::string())
: ps(new std::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:
std::string* ps;
int i;
size_t* use;
};
#endif
13.28
ex13_28.h
#ifndef CP5_ex13_28_h
#define CP5_ex13_28_h
#include
using std::string;
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) {
if (left) {
delete left;
left = nullptr;
}
if (right) {
delete right;
right = nullptr;
}
delete count;
count = nullptr;
}
}
private:
std::string value;
int* count;
TreeNode* left;
TreeNode* right;
};
class BinStrTree {
public:
BinStrTree() : root(new TreeNode()) {}
BinStrTree(const BinStrTree& bst) : root(new TreeNode(*bst.root)) {}
BinStrTree& operator=(const BinStrTree& bst);
~BinStrTree() { delete root; }
private:
TreeNode* root;
};
#endif
ex13_28.cpp
#include "ex13_28.h"
TreeNode& TreeNode::operator=(const TreeNode& rhs)
{
++*rhs.count;
if (--*count == 0) {
if (left) {
delete left;
left = nullptr;
}
if (right) {
delete right;
right = nullptr;
}
delete count;
count = nullptr;
}
value = rhs.value;
left = rhs.left;
right = rhs.right;
count = rhs.count;
return *this;
}
BinStrTree& BinStrTree::operator=(const BinStrTree& bst)
{
TreeNode* new_root = new TreeNode(*bst.root);
delete root;
root = new_root;
return *this;
}
13.29
swap( HasPtr&, HasPtr& )内部函数体调用的swap不是本身,而是标准库定义的swap,所以并不会导致递归循环。
swap(lhs.ps, rhs.ps)调用swap(std::string*, std::string*);
swap(lhs.i, rhs.i)调用swap(int, int);
都没有用到swap(HasPtr&, HasPtr&)。
13.30
#ifndef CP5_ex13_11_h
#define CP5_ex13_11_h
#include
#include
class HasPtr {
public:
friend void swap(HasPtr&, HasPtr&);
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 new_p = new std::string(*hp.ps);
delete ps;
ps = new_p;
i = hp.i;
return *this;
}
~HasPtr() { delete ps; }
void show() { std::cout << *ps << std::endl; }
private:
std::string* ps;
int i;
};
void swap(HasPtr& lhs, HasPtr& rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
std::cout << "call swap(HasPtr& lhs, HasPtr& rhs)" << std::endl;
}
#endif
13.31
sort时会调用swap.
#include
#include
#include
#include
class HasPtr {
public:
friend void swap(HasPtr&, HasPtr&);
friend bool operator<(const HasPtr& lhs, const HasPtr& rhs);
friend void show(std::vector& vec);
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=(HasPtr tmp)
{
this->swap(tmp);
return *this;
}
~HasPtr() { delete ps; }
void swap(HasPtr& rhs)
{
using std::swap;
swap(ps, rhs.ps);
swap(i, rhs.i);
std::cout << "call swap(HasPtr &rhs)" << std::endl;
}
private:
std::string* ps;
int i;
};
void swap(HasPtr& lhs, HasPtr& rhs)
{
lhs.swap(rhs);
}
bool operator<(const HasPtr& lhs, const HasPtr& rhs)
{
return *lhs.ps < *rhs.ps;
}
void show(std::vector& vec)
{
for (auto it = vec.begin(); it != vec.end(); ++it)
{
std::cout << *it->ps << std::endl;
}
}
int main(int argc, char**argv)
{
std::vector vec1;
HasPtr a("l");
HasPtr b("llll");
HasPtr c("lll");
vec1.push_back(a);
vec1.push_back(b);
vec1.push_back(c);
std::vector::iterator it1 = vec1.begin();
sort(vec1.begin(),vec1.end());
show(vec1);
return 0;
}
13.32
swap函数的设计是为了避免不必要的内存分配,类指针版本没有发生多余的内存分配,所以不会受益。
13.33
因为我们对传入的Folder实参要进行添加删除操作。
13.34
hpp
#ifndef CP5_ex13_34_36_37_h
#define CP5_ex13_34_36_37_h
#include
#include
class Folder;
class Message {
friend void swap(Message&, Message&);
friend void swap(Folder&, Folder&);
friend class Folder;
public:
explicit Message(const std::string& str = "") : contents(str) {}
Message(const Message&);
Message& operator=(const Message&);
~Message();
void save(Folder&);
void remove(Folder&);
void print_debug();
private:
std::string contents;
std::set folders;
void add_to_Folders(const Message&);
void remove_from_Folders();
void addFldr(Folder* f) { folders.insert(f); }
void remFldr(Folder* f) { folders.erase(f); }
};
void swap(Message&, Message&);
class Folder {
friend void swap(Message&, Message&);
friend void swap(Folder&, Folder&);
friend class Message;
public:
Folder() = default;
Folder(const Folder&);
Folder& operator=(const Folder&);
~Folder();
void print_debug();
private:
std::set msgs;
void add_to_Messages(const Folder&);
void remove_from_Messages();
void addMsg(Message* m) { msgs.insert(m); }
void remMsg(Message* m) { msgs.erase(m); }
};
void swap(Folder&, Folder&);
#endif // MESSAGE
cpp
#include "ex13_34_36_37.h"
#include
void swap(Message& lhs, Message& rhs)
{
using std::swap;
lhs.remove_from_Folders();
rhs.remove_from_Folders();
swap(lhs.folders, rhs.folders);
swap(lhs.contents, rhs.contents);
lhs.add_to_Folders(lhs);
rhs.add_to_Folders(rhs);
}
// Message Implementation
Message::Message(const Message& m) : contents(m.contents), folders(m.folders)
{
add_to_Folders(m);
}
Message& Message::operator=(const Message& rhs)
{
remove_from_Folders();
contents = rhs.contents;
folders = rhs.folders;
add_to_Folders(rhs);
return *this;
}
Message::~Message()
{
remove_from_Folders();
}
void Message::save(Folder& f)
{
addFldr(&f);
f.addMsg(this);
}
void Message::remove(Folder& f)
{
remFldr(&f);
f.remMsg(this);
}
void Message::print_debug()
{
std::cout << contents << std::endl;
}
void Message::add_to_Folders(const Message& m)
{
for (auto f : m.folders) f->addMsg(this);
}
void Message::remove_from_Folders()
{
for (auto f : folders) f->remMsg(this);
}
// Folder Implementation
void swap(Folder& lhs, Folder& rhs)
{
using std::swap;
lhs.remove_from_Messages();
rhs.remove_from_Messages();
swap(lhs.msgs, rhs.msgs);
lhs.add_to_Messages(lhs);
rhs.add_to_Messages(rhs);
}
Folder::Folder(const Folder& f) : msgs(f.msgs)
{
add_to_Messages(f);
}
Folder& Folder::operator=(const Folder& rhs)
{
remove_from_Messages();
msgs = rhs.msgs;
add_to_Messages(rhs);
return *this;
}
Folder::~Folder()
{
remove_from_Messages();
}
void Folder::print_debug()
{
for (auto m : msgs) std::cout << m->contents << " ";
std::cout << std::endl;
}
void Folder::add_to_Messages(const Folder& f)
{
for (auto m : f.msgs) m->addFldr(this);
}
void Folder::remove_from_Messages()
{
for (auto m : msgs) m->remFldr(this);
}
Test
#include "ex13_34_36_37.h"
int main()
{
Message firstMail("hello");
Message signInMail("welcome to cppprimer");
Folder mailBox;
firstMail.print_debug(); // print: "hello"
firstMail.save(mailBox); // send to mailBox
mailBox.print_debug(); // print: "hello"
signInMail.print_debug(); // print "welcome to cppprimer"
signInMail.save(mailBox); // send to mailBox
mailBox.print_debug(); // print "welcome to cppprimer hello"
firstMail = firstMail; // test for assignment to self.
firstMail.print_debug(); // print "hello"
mailBox.print_debug(); // print "welcome to cppprimer hello"
}
13.35
当拷贝一个Message 创造一个副本时,得到的副本没有与原Message出现在相同的Folder中。
13.36~13.37
见13.34
13.38
因为在这个例子中,swap功能是特殊的,它将每个消息的指针从它原来所在的Folder中删除,然后交换成员,将每个消息的指针添加到它的新的Folder中。但是拷贝赋值运算符只需清除自身,拷贝成员,添加Folder中,rhs不需要清除和添加Folder。如果这里使用拷贝并交换是低效的。
13.39
hpp
#ifndef CP5_EX_13_39_H_
#define CP5_EX_13_39_H_
#include
#include
class StrVec {
public:
StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {}
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; }
std::string* begin() const { return elements; }
std::string* end() const { return first_free; }
void reserve(size_t new_cap);
void resize(size_t count);
void resize(size_t count, const std::string&);
private:
std::pair alloc_n_copy(const std::string*,
const std::string*);
void free();
void chk_n_alloc()
{
if (size() == capacity()) reallocate();
}
void reallocate();
void alloc_n_move(size_t new_cap);
private:
std::string* elements;
std::string* first_free;
std::string* cap;
std::allocator alloc;
};
#endif
Cpp
#include "ex13_39.h"
void StrVec::push_back(const std::string& s)
{
chk_n_alloc();
alloc.construct(first_free++, s);
}
std::pair 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);
alloc.deallocate(elements, cap - elements);
}
}
StrVec::StrVec(const StrVec& rhs)
{
auto newdata = alloc_n_copy(rhs.begin(), rhs.end());
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec::~StrVec()
{
free();
}
StrVec& 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;
}
void StrVec::alloc_n_move(size_t new_cap)
{
auto newdata = alloc.allocate(new_cap);
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 + new_cap;
}
void StrVec::reallocate()
{
auto newcapacity = size() ? 2 * size() : 1;
alloc_n_move(newcapacity);
}
void StrVec::reserve(size_t new_cap)
{
if (new_cap <= capacity()) return;
alloc_n_move(new_cap);
}
void StrVec::resize(size_t count)
{
resize(count, std::string());
}
void StrVec::resize(size_t count, const std::string& s)
{
if (count > size()) {
if (count > capacity()) reserve(count * 2);
for (size_t i = size(); i != count; ++i)
alloc.construct(first_free++, s);
}
else if (count < size()) {
while (first_free != elements + count) alloc.destroy(--first_free);
}
}
13.40
Hpp
#ifndef CP5_EX_13_40_H_
#define CP5_EX_13_40_H_
#include
#include
#include
class StrVec {
public:
StrVec() : elements(nullptr), first_free(nullptr), cap(nullptr) {}
StrVec(const StrVec&);
StrVec(std::initializer_list);
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; }
std::string* begin() const { return elements; }
std::string* end() const { return first_free; }
void reserve(size_t new_cap);
void resize(size_t count);
void resize(size_t count, const std::string&);
private:
std::pair alloc_n_copy(const std::string*,
const std::string*);
void free();
void chk_n_alloc()
{
if (size() == capacity()) reallocate();
}
void reallocate();
void alloc_n_move(size_t new_cap);
void range_initialize(const std::string*, const std::string*);
private:
std::string* elements;
std::string* first_free;
std::string* cap;
std::allocator alloc;
};
#endif
Cpp
#include "ex13_40.h"
void StrVec::push_back(const std::string& s)
{
chk_n_alloc();
alloc.construct(first_free++, s);
}
std::pair 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);
alloc.deallocate(elements, cap - elements);
}
}
void StrVec::range_initialize(const std::string* first, const std::string* last)
{
auto newdata = alloc_n_copy(first, last);
elements = newdata.first;
first_free = cap = newdata.second;
}
StrVec::StrVec(const StrVec& rhs)
{
range_initialize(rhs.begin(), rhs.end());
}
StrVec::StrVec(std::initializer_list il)
{
range_initialize(il.begin(), il.end());
}
StrVec::~StrVec()
{
free();
}
StrVec& 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;
}
void StrVec::alloc_n_move(size_t new_cap)
{
auto newdata = alloc.allocate(new_cap);
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 + new_cap;
}
void StrVec::reallocate()
{
auto newcapacity = size() ? 2 * size() : 1;
alloc_n_move(newcapacity);
}
void StrVec::reserve(size_t new_cap)
{
if (new_cap <= capacity()) return;
alloc_n_move(new_cap);
}
void StrVec::resize(size_t count)
{
resize(count, std::string());
}
void StrVec::resize(size_t count, const std::string& s)
{
if (count > size()) {
if (count > capacity()) reserve(count * 2);
for (size_t i = size(); i != count; ++i)
alloc.construct(first_free++, s);
}
else if (count < size()) {
while (first_free != elements + count) alloc.destroy(--first_free);
}
}
13.41
|a|b|c|d|f|..............|
^ ^ ^
elements first_free cap
// if use alloc.construct(first_free++, "g");
|a|b|c|d|f|g|.............|
^ ^ ^
elements first_free cap
// if use alloc.construct(++first_free, "g");
|a|b|c|d|f|.|g|............|
^ ^ ^ ^
elements | first_free cap
|
"unconstructed"
13.42
ex13_42_TextQuery.h
#ifndef CP5_TEXTQUERY_H_
#define CP5_TEXTQUERY_H_
#include
#include
#include
#include
#include
ex13_42_TextQuery.cpp
#include "ex13_42_TextQuery.h"
#include
#include
using std::string;
TextQuery::TextQuery(std::ifstream& ifs) : input(new StrVec)
{
size_t lineNo = 0;
for (string line; std::getline(ifs, line); ++lineNo) {
input->push_back(line);
std::istringstream line_stream(line);
for (string text, word; line_stream >> text; word.clear()) {
// avoid read a word followed by punctuation(such as: word, )
std::remove_copy_if(text.begin(), text.end(),
std::back_inserter(word), ispunct);
// use reference avoid count of shared_ptr add.
auto& nos = result[word];
if (!nos) nos.reset(new std::set);
nos->insert(lineNo);
}
}
}
QueryResult TextQuery::query(const string& str) const
{
// use static just allocate once.
static std::shared_ptr> nodate(new std::set);
auto found = result.find(str);
if (found == result.end())
return QueryResult(str, nodate, input);
else
return QueryResult(str, found->second, input);
}
std::ostream& print(std::ostream& out, const QueryResult& qr)
{
out << qr.word << " occurs " << qr.nos->size()
<< (qr.nos->size() > 1 ? " times" : " time") << std::endl;
for (auto i : *qr.nos)
out << "\t(line " << i + 1 << ") " << qr.input->at(i) << std::endl;
return out;
}
TEST
#include "ex13_42_TextQuery.h"
#include
void runQueries(std::ifstream& infile)
{
TextQuery tq(infile);
while (true) {
std::cout << "enter word to look for, or q to quit: ";
std::string s;
if (!(std::cin >> s) || s == "q") break;
print(std::cout, tq.query(s)) << std::endl;
}
}
int main()
{
std::ifstream file("../data/storyDataFile.txt");
runQueries(file);
}
13.43
for_each (elements, first_free, [this] ( std::string& rhs) { alloc.destroy(&rhs);});
新版本更好。与旧的相比,它不需要担心顺序和递减,所以更简单方便。使用这种方法唯一要做的就是添加“&”来构建指向字符串指针的指针。
13.44
Hpp
#ifndef CP5_STRING_H__
#define CP5_STRING_H__
#include
class String {
public:
String() : String("") {}
String(const char*);
String(const String&);
String& operator=(const String&);
~String();
const char* c_str() const { return elements; }
size_t size() const { return end - elements; }
size_t length() const { return end - elements - 1; }
private:
std::pair alloc_n_copy(const char*, const char*);
void range_initializer(const char*, const char*);
void free();
private:
char* elements;
char* end;
std::allocator alloc;
};
#endif
Cpp
#include "ex13_44_47.h"
#include
#include
std::pair String::alloc_n_copy(const char* b, const char* e)
{
auto str = alloc.allocate(e - b);
return {str, std::uninitialized_copy(b, e, str)};
}
void String::range_initializer(const char* first, const char* last)
{
auto newstr = alloc_n_copy(first, last);
elements = newstr.first;
end = newstr.second;
}
String::String(const char* s)
{
char* sl = const_cast(s);
while (*sl) ++sl;
range_initializer(s, ++sl);
}
String::String(const String& rhs)
{
range_initializer(rhs.elements, rhs.end);
std::cout << "copy constructor" << std::endl;
}
void String::free()
{
if (elements) {
std::for_each(elements, end, [this](char& c) { alloc.destroy(&c); });
alloc.deallocate(elements, end - elements);
}
}
String::~String()
{
free();
}
String& String::operator=(const String& rhs)
{
auto newstr = alloc_n_copy(rhs.elements, rhs.end);
free();
elements = newstr.first;
end = newstr.second;
std::cout << "copy-assignment" << std::endl;
return *this;
}
TEST
#include "ex13_44_47.h"
#include
#include
// Test reference to http://coolshell.cn/articles/10478.html
void foo(String x)
{
std::cout << x.c_str() << std::endl;
}
void bar(const String& x)
{
std::cout << x.c_str() << std::endl;
}
String baz()
{
String ret("world");
return ret;
}
int main()
{
char text[] = "world";
String s0;
String s1("hello");
String s2(s0);
String s3 = s1;
String s4(text);
s2 = s1;
foo(s1);
bar(s1);
foo("temporary");
bar("temporary");
String s5 = baz();
std::vector svec;
svec.reserve(8);
svec.push_back(s0);
svec.push_back(s1);
svec.push_back(s2);
svec.push_back(s3);
svec.push_back(s4);
svec.push_back(s5);
svec.push_back(baz());
svec.push_back("good job");
for (const auto& s : svec) {
std::cout << s.c_str() << std::endl;
}
}
13.45
左值引用:引用可以绑定左值(常规引用)
右值引用:引用绑定到临时对象上,该对象将要销毁。
我们可以将一个右值引用绑定到要求转换的表达式,字面值常量或是返回右值的表达式,但是不能将一个右值引用直接绑定到一个左值上。
返回左值的表达式:返回左值引用的函数,赋值,下标,解引用,前置递增/递减运算符。
生成右值:返回非引用类型的函数,算术,关系,位以及后置递增/递减运算符。
13.46
int f();
vector vi(100);
int&& r1 = f();
int& r2 = vi[0];
int& r3 = r1;
int&& r4 = vi[0] * f();
13.47
见13.44
13.48
#include "ex13_44_47.h"
#include
#include
// Test reference to http://coolshell.cn/articles/10478.html
void foo(String x)
{
std::cout << x.c_str() << std::endl;
}
void bar(const String& x)
{
std::cout << x.c_str() << std::endl;
}
String baz()
{
String ret("world");
return ret;
}
int main()
{
char text[] = "world";
String s0;
String s1("hello");
String s2(s0);
String s3 = s1;
String s4(text);
s2 = s1;
foo(s1);
bar(s1);
foo("temporary");
bar("temporary");
String s5 = baz();
std::vector svec;
svec.reserve(8);
svec.push_back(s0);
svec.push_back(s1);
svec.push_back(s2);
svec.push_back(s3);
svec.push_back(s4);
svec.push_back(s5);
svec.push_back(baz());
svec.push_back("good job");
for (const auto& s : svec) {
std::cout << s.c_str() << std::endl;
}
}
13.49
StrVec
StrVec::StrVec(StrVec&& s) NOEXCEPT : elements(s.elements),
first_free(s.first_free),
cap(s.cap)
{
// leave s in a state in which it is safe to run the destructor.
s.elements = s.first_free = s.cap = nullptr;
}
StrVec& 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::operator=(const String& rhs)
{
auto newstr = alloc_n_copy(rhs.elements, rhs.end);
free();
elements = newstr.first;
end = newstr.second;
return *this;
}
String::String(String&& s) NOEXCEPT : elements(s.elements), end(s.end)
{
s.elements = s.end = nullptr;
}
String& String::operator=(String&& rhs) NOEXCEPT
{
if (this != &rhs) {
free();
elements = rhs.elements;
end = rhs.end;
rhs.elements = rhs.end = nullptr;
}
return *this;
}
Message
Folder::Folder(Folder&& f)
{
move_Messages(&f);
}
Folder& Folder::operator=(Folder&& f)
{
if (this != &f) {
remove_from_Messages();
move_Messages(&f);
}
return *this;
}
13.50
String baz()
{
String ret("world");
return ret; // first avoided
}
String s5 = baz(); // second avoided
13.51
因为函数调用的结果是一个右值。
13.52
赋值运算符有一个非引用参数,会进行拷贝初始化,左值被拷贝,右值被移动。
13.53
hpp
#ifndef CP5_ex13_53_h
#define CP5_ex13_53_h
#include
class HasPtr {
public:
friend void swap(HasPtr&, HasPtr&);
HasPtr(const std::string& s = std::string());
HasPtr(const HasPtr& hp);
HasPtr(HasPtr&& p) noexcept;
HasPtr& operator=(HasPtr rhs);
// HasPtr& operator=(const HasPtr &rhs);
// HasPtr& operator=(HasPtr &&rhs) noexcept;
~HasPtr();
private:
std::string* ps;
int i;
};
#endif // CP5_ex13_53_h
cpp
#include "ex13_53.h"
#include
inline void swap(HasPtr& lhs, HasPtr& rhs)
{
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i, rhs.i);
std::cout << "call swap" << std::endl;
}
HasPtr::HasPtr(const std::string& s) : ps(new std::string(s)), i(0)
{
std::cout << "call constructor" << std::endl;
}
HasPtr::HasPtr(const HasPtr& hp) : ps(new std::string(*hp.ps)), i(hp.i)
{
std::cout << "call copy constructor" << std::endl;
}
HasPtr::HasPtr(HasPtr&& p) noexcept : ps(p.ps), i(p.i)
{
p.ps = 0;
std::cout << "call move constructor" << std::endl;
}
HasPtr& HasPtr::operator=(HasPtr rhs)
{
swap(*this, rhs);
return *this;
}
// HasPtr& HasPtr::operator=(const HasPtr &rhs)
//{
// auto newp = new std::string(*rhs.ps);
// delete ps;
// ps = newp;
// i = rhs.i;
// std::cout << "call copy assignment" << std::endl;
// return *this;
//}
// HasPtr& HasPtr::operator=(HasPtr &&rhs) noexcept
//{
// if (this != &rhs)
// {
// delete ps;
// ps = rhs.ps;
// i = rhs.i;
// rhs.ps = nullptr;
// std::cout << "call move assignment" << std::endl;
// }
// return *this;
//}
HasPtr::~HasPtr()
{
std::cout << "call destructor" << std::endl;
delete ps;
}
more information
事实上,这题说效率不高的原因是用copy-swap的方法代替两个本来要写的函数(拷贝赋值和移动赋值)。
对于传递一个右值引用来说:
1:首先需要调用移动构造函数,将右值引用的指针成员转移到到临时变量形参rhs中。
2:之后再调用swap,再将rhs中的指针成员转移到*this中。
期间指针成员的值被copy了两次
但是如果单独写两个函数(一个拷贝赋值,一个移动赋值),那么在移动赋值函数中,可以只copy一次指针成员的值。
13.54
'operator='的重载不明确
error: ambiguous overload for 'operator=' (operand types are 'HasPtr' and 'std::remove_reference::type {aka HasPtr}')
hp1 = std::move(*pH);
13.55
void push_back(string &&s) { data->push_back(std::move(s)); }
13.56
因为这里的局部变量ret是一个lvalue,所以当我们调用ret.sorted()时,实际上并不像预期的那样调用成员函数 Foo Foo::sorted() &&,而是调用 Foo Foo::sorted() const &,因此,代码将被捕获到递归中,并导致致命的堆栈溢出。
13.57
ok,右值调用移动版本。
13.58
#include
#include
#include
using std::vector;
using std::sort;
class Foo {
public:
Foo sorted()&&;
Foo sorted() const&;
private:
vector data;
};
Foo Foo::sorted() &&
{
sort(data.begin(), data.end());
std::cout << "&&" << std::endl; // debug
return *this;
}
Foo Foo::sorted() const &
{
// Foo ret(*this);
// sort(ret.data.begin(), ret.data.end());
// return ret;
std::cout << "const &" << std::endl; // debug
// Foo ret(*this);
// ret.sorted(); // Exercise 13.56
// return ret;
return Foo(*this).sorted(); // Exercise 13.57
}
int main()
{
Foo().sorted(); // call "&&"
Foo f;
f.sorted(); // call "const &"
}