📔 C++ Primer 0x0D 练习题解
更好的阅读体验(实时更新与修正)
推荐阅读 《C++ Primer 5th》知识点总结&练习题解
13.1 拷贝、赋值与销毁
13.1.1 拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用(且一般是一个const
的引用),且任何额外参数都有默认值,则此构造函数是拷贝构造函数
当拷贝初始化的时候会使用拷贝构造函数
拷贝初始化不仅在我们用=
定义变量时发生,在下列情况也会发生
- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或聚合类中的成员
- 某些类型还会对它们所分配的对象使用拷贝初始化(
insert
、push
会进行拷贝初始化,emplace
会直接初始化)
Sales_data::Sales_data(Sales_data rhs);
参数应该是自身类型的引用
StrBlob
使用了shared_ptr
引用计数加一
StrBlobPtr
使用了weak_ptr
引用计数不增加
其他数据成员直接拷贝
Point global;
Point foo_bar(Point arg) // 1
{
Point local = arg, *heap = new Point(global); // 2,3
*heap = local; //4
Point pa[4] = { local, *heap }; // 5, 6
return *heap; // 7
}
- 将一个对象作为实参传递给一个非引用类型的形参(1)
- 使用=定义变量(2,3,4)
- 用花括号列表初始化一个数组中的元素或聚合类中的成员(5,6 用了两次)
- 从一个返回类型为非引用类型的函数返回一个对象(7)
class HasPtr {
public:
HasPtr(const std::string& s = std::string()):
ps(new std::string(s)), i(0) { }
private:
std::string *ps;
int i;
}
#include <string>
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;
};
13.1.2 拷贝赋值运算符
- 拷贝运算符是函数
operator=
的一个重载,拷贝赋值运算符接受一个与其所在类相同类型的参数
在使用赋值运算时会使用拷贝赋值运算符
合成拷贝赋值运算符
- 如果一个类未定义自己的拷贝赋值运算符,编译器会为它生产一个合成拷贝赋值运算符
- 对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值
- 一般的,拷贝赋值运算符会将右侧运算对象的每个非
static
成员赋予左侧运算对象的对应成员,这一工作是通过成员类型的拷贝赋值运算符来完成的。对于数组类型的成员,逐个赋值数组元素 - 合成拷贝赋值运算符返回一个指向左侧运算对象的引用
StrBlob
:会通过shared_ptr
类的拷贝赋值运算符将shared_ptr
拷贝赋值,且其计数器自增。
StrBlobPtr
:会通过weak_ptr
类的拷贝赋值运算符将weak_ptr
拷贝赋值。curr
调用内置类型的赋值运算符。
记住处理自赋值和异常安全问题,这个是类值拷贝赋值运算符编写方式
#include <string>
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& rhs){
auto newp = new std::string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
~HasPtr() { delete ps;}
private:
std::string *ps;
int i;
};
- 析构函数:释放对象使用的资源,并销毁对象的非
static
数据成员 - 析构函数是类的一个成员函数,名字波浪号接类名,没有返回值,也不接受参数,不能被重载,对一个给定类唯一
析构函数完成什么工作
- 在析构函数中,首先执行函数体,然后销毁成员,成员按初始化顺序的逆序销毁
- 不像构造函数有初始化列表,析构函数的析构部分是隐式的,成员销毁时发生什么完全依赖成员的类型
- 销毁类类型的成员执行类类型的析构函数
- 内置类型没有析构函数,销毁内置类型成员什么也不需要做
- 隐式销毁一个内置指针类型的成员不会
delete
它所指向的对象 - 与普通指针不同,智能指针是类类型,所以具有析构函数。智能指针成员在析构阶段会被自动销毁
合成析构函数
- 当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数
- 对于某些类,合成析构函数被用来阻止该类型的对象被销毁
- 一般,合成析构函数的函数体为空
- **认识到析构函数体自身并不直接销毁成员很重要。成员是在析构函数体之后隐含的析构阶段被销毁的。**在整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分进行的
StrBlob
:shared_ptr
的引用计数减少
StrBlobPtr
:不影响引用计数
#include <string>
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& rhs){
auto newp = new std::string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
~HasPtr(){delete ps;}
private:
std::string *ps;
int i;
};
bool fcn(const Sales_data *trans, Sales_data accum)
{
Sales_data item1(*trans), item2(accum);
return item1.isbn() != item2.isbn();
}
3次
item1
、item2
、accum
离开作用域被销毁
trans
是指向对象的指针,析构函数不会执行
struct X {
X() {std::cout << "X()" << std::endl;}
X(const X&) {std::cout << "X(const X&)" << std::endl;}
};
给 X
添加拷贝赋值运算符和析构函数,并编写一个程序以不同的方式使用 X
的对象:将它们作为非引用参数传递;动态分配它们;将它们存放于容器中;诸如此类。观察程序的输出,直到你确认理解了什么时候会使用拷贝控制成员,以及为什么会使用它们。当你观察程序输出时,记住编译器可以略过对拷贝构造函数的调用。
#include <iostream>
#include <string>
#include <vector>
struct X {
X() {std::cout << "X()" << std::endl;}
X(const X&) {std::cout << "X(const X&)" << std::endl;}
X& operator=(const X&rhs){std::cout << "X& operator=(const X&rhs)" << std::endl;}
~X(){std::cout << "~X()" << std::endl;}
};
void f(X a,const X& b){
std::vector<X>v;
std::cout << "push_back a" << std::endl;
v.push_back(a);
std::cout << "push_back b" << std::endl;
v.push_back(b);
}
int main(){
std::cout << "create pb" << std::endl;
X a;
std::cout << "create pb" << std::endl;
X* pb = new X(a);
std::cout << "f(a,*pb)" << std::endl;
f(a,*pb);
delete pb;
return 0;
}
13.1.4 三/五法则
void f (numbered s) { cout << s.mysn < endl; }
则下面代码输出什么内容?
numbered a, b = a, c = b;
f(a); f(b); f(c);
输出三个一样的数字
输出三个不一样的数字,但是和 a,b,c 也没有关系
输出三个不一样的数字,分别是a,b,c的数字
3.14
#include <iostream>
#include <string>
#include <vector>
class numbered{
public:
friend void f(numbered s);
numbered() {mysn = num++;}
private:
static int num;
int mysn;
};
int numbered::num = 0;
void f (numbered s) { std::cout << s.mysn << std::endl; }
int main(){
numbered a,b=a,c=b;
f(a);f(b);f(c);
return 0;
}
输出三个0
3.15
#include <iostream>
#include <string>
#include <vector>
class numbered{
public:
friend void f(numbered s);
numbered() {mysn = num++;}
numbered(const numbered &s){
mysn = num++;
}
private:
static int num;
int mysn;
};
int numbered::num = 0;
void f (numbered s) { std::cout << s.mysn << std::endl; }
int main(){
numbered a,b=a,c=b;
f(a);f(b);f(c);
return 0;
}
输出3,4,5
3.16
#include <iostream>
#include <string>
#include <vector>
class numbered{
public:
friend void f(const numbered &s);
numbered() {mysn = num++;}
numbered(const numbered &s){
mysn = num++;
}
private:
static int num;
int mysn;
};
int numbered::num = 0;
void f (const numbered &s) { std::cout << s.mysn << std::endl; }
int main(){
numbered a,b=a,c=b;
f(a);f(b);f(c);
return 0;
}
输出0,1,2
13.1.6 阻止拷贝
class Employee{
public:
Employee(){id = id_num++;}
Employee(const std::string &s):name(s),id(id_num){
id_num++;
}
private:
std::string name;
int id;
static int id_num;
};
static int id_num = 1000;
不需要拷贝,雇员没有必要因为拷贝而递增编号。将拷贝构造和赋值构造运算符定义为删除来阻止拷贝和赋值
class Employee{
public:
Employee(){id = id_num++;}
Employee(const std::string &s):name(s),id(id_num){
id_num++;
}
Employee(const Employee &)=delete;
Employee& operator=(const Employee&)=delete;
private:
std::string name;
int id;
static int id_num;
};
static int id_num = 1000;
因为这两个类中使用的是智能指针,因此在拷贝时,类的所有成员都将被拷贝,在销毁时所有成员也将被销毁。
不需要,因为用的智能指针,合成版本可以自动控制内存释放
当我们决定一个类是否要定义他自己版本的拷贝控制成员时,一个基本原则就是确定这个类是否需要析构函数,需要析构函数的类也需要拷贝和赋值操作
13.2 拷贝控制和资源管理
同13.8
13.2.1 行为像值的类
因为先把整章看了一遍才写练习的,所以基本没区别。
朴素一些的写法是用if
判一下是不是自赋值,高级一点的写法是用swap
来实现,后面会有
三/五法则,拷贝成员控制的几个操作应该看成一个整体,一般要写就都写
未定义析构函数会内存泄漏
未定义拷贝构造函数会只拷贝指针的值,几个指针指向同一个地址
拷贝构造函数和拷贝赋值运算符要重新动态分配内存
因为用的智能指针,可以自动控制内存释放,引用计数为0自动销毁对象,所以不需要析构函数
StrBlob
里添加
//拷贝构造
StrBlob(const StrBlob& sb):data(std::make_shared<std::vector<std::string>>(*sb.data)){}
//拷贝赋值
StrBlob& operator=(const StrBlob& sb){
data = std::make_shared<std::vector<std::string>>(*sb.data);
return *this;
}
13.2.2 定义行为像指针的类
class HasPtr {
public:
HasPtr(const std::string &s = std::string())
:ps(new std::string(s)),i(0),use(new std::size_t(1)){}
HasPtr(const HasPtr &p)
:ps(p.ps),i(p.i),use(p.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;
std::size_t *use;//引用计数
};
(a)
class TreeNode {
private:
std::string value;
int *count;
TreeNode *left;
TreeNode *right;
};
(b)
class BinStrTree{
private:
TreeNode *root;
};
#include <cstddef>
#include <string>
class TreeNode {
public:
TreeNode()
:value(std::string()),count(new int(1)),left(nullptr),right(nullptr){}
TreeNode(const TreeNode& t)
:value(t.value),count(t.count),left(t.left),right(t.right){
++*count;
}
TreeNode& operator=(const TreeNode& rhs){
++*rhs.count;
if(--*count == 0){
delete left;
delete right;
delete count;
}
value = rhs.value;
left = rhs.left;
right = rhs.right;
return *this;
}
~TreeNode(){
if(--*count == 0){
delete left;
delete right;
delete count;
}
}
private:
std::string value;
int* count;
TreeNode *left;
TreeNode *right;
};
class BinStrTree{
public:
BinStrTree():root(nullptr){}
BinStrTree(const BinStrTree &bst):root(new TreeNode(*bst.root)){}
BinStrTree& operator=(const BinStrTree& rhs){
auto newroot = new TreeNode(*rhs.root);
delete root;
root = newroot;
return *this;
}
~BinStrTree(){delete root;}
private:
TreeNode *root;
};
13.3 交换操作
因为没有给内置类型编写自定义的swap
所以优先匹配到的是标准库的std::swap
#include <string>
#include <iostream>
class HasPtr {
public:
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& rhs){
auto newp = new std::string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
~HasPtr() {delete ps;}
private:
std::string *ps;
int i;
};
inline
void swap(HasPtr& lhs, HasPtr& rhs){
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i,rhs.i);
std::cout << "using HasPtr's swap" << std::endl;
}
#include <string>
#include <iostream>
#include <vector>
#include <algorithm>
class HasPtr {
public:
friend void swap(HasPtr&, HasPtr&);
friend bool operator<(const HasPtr& lhs,const HasPtr& rhs);
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=(HasPtr rhs){//赋值拷贝要写成swap形式会被用到
swap(*this,rhs);
return *this;
}
~HasPtr(){delete ps;}
std::string show(){return *ps;}
private:
std::string *ps;
int i;
};
inline
void swap(HasPtr& lhs, HasPtr& rhs){
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i,rhs.i);
std::cout << "using HasPtr's swap" << std::endl;
}
inline
bool operator<(const HasPtr& lhs,const HasPtr& rhs){
return *lhs.ps < *rhs.ps;
}
int main(){
std::vector<HasPtr>vec;
HasPtr a("fbc"),b("cde"),c("avpq");
vec.push_back(a);
vec.push_back(b);
vec.push_back(c);
sort(vec.begin(),vec.end());
for(auto i:vec){
std::cout <<i.show() << std::endl;
}
}
输出结果
using HasPtr's swap
using HasPtr's swap
using HasPtr's swap
using HasPtr's swap
using HasPtr's swap
avpq
cde
fbc
不会,类值版本采用自己写的swap
通过交换指针避免了重新内存分配,类指针版本本身就是指针,所以没啥收益
13.4 拷贝控制实例
因为save
和remove
是要操作到某个具体的对象,而不是某一类
#include <string>
#include <iostream>
#include <set>
#include <vector>
class Folder;
class Message{
public:
friend void swap(Message &lhs,Message &rhs);
friend class Folder;
public:
explicit Message(const std::string &str = ""):contents(str){}
Message(const Message&);
Message& operator=(const Message&);
~Message();
void save(Folder &);//向指定folder添加这条消息
void remove(Folder &);//从指定folder删除这条消息
private:
std::string contents;//保存内容
std::set<Folder*>folders;//保存这条message在哪些folder里
void add_to_Folders(const Message&);
void remove_from_Folders();
};
class Folder{
public:
friend void swap(Message &lhs,Message &rhs);
friend class Message;
public:
Folder()= default;
Folder(const Folder &f);
Folder& operator=(const Folder &f);
~Folder();
private:
std::set<Message*>messages;
void add_to_Message(const Folder&);
void remove_from_Message();
void addMsg(const Message *);
void remMsg(const Message *);
};
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);
}
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 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.messages)
m->save(*this);
}
Folder::Folder(const Folder &f) : messages(f.messages) {
add_to_Message(f);
}
void Folder::remove_from_Message() {
for (auto m : messages)
m->remove(*this);
}
Folder::~Folder()
{
remove_from_Message();
}
Folder &Folder::operator=(const Folder &rhs) {
remove_from_Message();
messages = rhs.messages;
add_to_Message(rhs);
return *this;
}
int main(){
return 0;
}
合成的拷贝控制成员可能导致赋值后已存在的Folders
和Message
不同步
对于动态分配内存的例子来说,拷贝交换方式是一种简洁的设计。而这里的 Message
类并不需要动态分配内存,用拷贝交换方式只会增加实现的复杂度。
13.5 动态内存管理类
参考了applenob
的答案,感觉有几处代码的重构简化写的很不错(比如提炼了copy_n_move
)
头文件
#ifndef StrVec_H
#define StrVec_H
#include <cstdlib>
#include <string>
#include <iostream>
#include <vector>
#include <memory>
#include <utility>
#include <initializer_list>
class StrVec{
public:
StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}
StrVec(const StrVec&);
StrVec& operator=(const StrVec&);
~StrVec();
public:
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& s);
private:
static std::allocator<std::string>alloc;
void chk_n_alloc(){
if (size() == capacity())reallocate();
}
std::pair<std::string*,std::string*>alloc_n_copy(const std::string*,const std::string*);
void free();
void reallocate();
void alloc_n_move(size_t new_cap);
private:
std::string *elements;
std::string *first_free;
std::string *cap;
};
#endif
实现文件
#include "StrVec.hpp"
#include <memory>
#include <string>
void StrVec::push_back(const std::string& s){
chk_n_alloc();
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);
alloc.deallocate(elements,cap-elements);
}
}
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 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);
}
}
#ifndef StrVec_H
#define StrVec_H
#include <cstdlib>
#include <string>
#include <iostream>
#include <vector>
#include <memory>
#include <utility>
#include <initializer_list>
class StrVec{
public:
StrVec():elements(nullptr),first_free(nullptr),cap(nullptr){}
StrVec(const StrVec&);
StrVec& operator=(const StrVec&);
~StrVec();
StrVec(std::initializer_list<std::string>);
public:
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& s);
private:
static std::allocator<std::string>alloc;
void chk_n_alloc(){
if (size() == capacity())reallocate();
}
std::pair<std::string*,std::string*>alloc_n_copy(const std::string*,const std::string*);
void free();
void reallocate();
void alloc_n_move(size_t new_cap);
void range_initialize(const std::string *first, const std::string *last);
private:
std::string *elements;
std::string *first_free;
std::string *cap;
};
#endif
#include "StrVec.hpp"
#include <memory>
#include <string>
void StrVec::push_back(const std::string& s){
chk_n_alloc();
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);
alloc.deallocate(elements,cap-elements);
}
}
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 data = alloc_n_copy(rhs.begin(),rhs.end());
free();
elements = data.first;
first_free = cap = data.second;
return *this;
}
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(std::initializer_list<std::string> il)
{
range_initialize(il.begin(), il.end());
}
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);
}
}
会跳过一个空位置
void StrVec::free(){
if(elements){
for_each(elements,first_free,[this](std::string& s){alloc.destroy(&s);})
alloc.deallocate(elements,cap-elements);
}
}
for_each
+lambda
更简洁,而且可以避免越界
参考书上的StrVec
和applenon
MyString.hpp
#ifndef MyString_H
#define MyString_H
#include <memory>
#include <algorithm>
#include <iterator>
class MyString{
public:
MyString():MyString(""){}
MyString(const char*);
MyString(const MyString&);
MyString& operator=(const MyString&);
~MyString();
public:
size_t size()const{return ends-elements;}
const char* begin()const{return elements;}
const char* end()const{return ends;}
private:
std::allocator<char> alloc;
std::pair<char*,char*>alloc_n_copy(const char*,const char*);
void free();
void range_initializer(const char *first, const char *last);
private:
char* elements;
char* ends;
};
#endif
MyString.cpp
#include "MyString.hpp"
std::pair<char*,char*>
MyString::alloc_n_copy(const char*b,const char*e){
auto data = alloc.allocate(e-b);
return {data,std::uninitialized_copy(b, e,data)};
}
void MyString::free(){
if(elements){
std::for_each(elements,ends,[this](char &c){alloc.destroy(&c);});
alloc.deallocate(elements,ends-elements);
}
}
void MyString::range_initializer(const char *first, const char *last){
auto newstr = alloc_n_copy(first, last);
elements = newstr.first;
ends = newstr.second;
}
MyString::MyString(const char* s){
char * sl = const_cast<char*>(s);
while(*sl)
++sl;
range_initializer(s,++sl);
}
MyString::~MyString(){free();}
MyString& MyString::operator=(const MyString &rhs){
auto data = alloc_n_copy(rhs.begin(),rhs.end());
free();
elements = data.first;
ends = data.second;
std::cout << "call MyString& MyString::operator=(const MyString &rhs)" << std::endl;
return *this;
}
MyString::MyString(const MyString& ms){
range_initializer(ms.begin(),ms.end());
std::cout << "MyString::MyString(const MyString& ms)" << std::endl;
}
Test.cpp
#include <memory>
#include <vector>
#include <iostream>
#include "MyString.hpp"
void foo(MyString x)
{
std::cout << x.begin() << std::endl;
}
void bar(const MyString& x)
{
std::cout << x.begin() << std::endl;
}
MyString baz()
{
MyString ret("world");
return ret;
}
int main()
{
char text[] = "world";
MyString s0;
MyString s1("hello");
MyString s2(s0);
MyString s3 = s1;
MyString s4(text);
s2 = s1;
foo(s1);
bar(s1);
foo("temporary");
bar("temporary");
MyString s5 = baz();
std::vector<MyString> 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.begin() << std::endl;
}
}
makefile
main:Test.o MyString.o
clang++ Test.o MyString.o -o main -g
Test.o:Test.cpp
clang++ -c Test.cpp -o Test.o
MyString.o:MyString.cpp MyString.hpp
clang++ -c MyString.cpp -o MyString.o
clean:
rm *.o main
13.6 对象移动
13.6.1 右值引用
左值引用:一般引用
右值引用:绑定到右值上的引用
- 右值引用必须绑定在右值(要求转换的表达式,字面常量,返回右值的表达式),不能直接绑定到一个左值上,通过
&&
而不是&
来获得- 返回左值引用的函数,连通赋值、下标、解引用和前置递增/递减运算符,都是返回左值表达式的例子
- 返回非引用类型的函数,连通算术、关系、位以及后值递增/递减运算符,都生成右值。不能将左值引用绑定到这类表达式上,但可以将一个
cosnt
的左值引用或者一个右值引用绑定到这类表达式上
- 右值引用只能绑定到一个将要销毁的对象上(就好比打游戏,对面有武器,对面要死了你才可以去捡武器)
- 左值持久:对象的身份,具有持久的状态
- 右值短暂:对象的值,要么是字面常量,要么是表达式求值过程中创建的临时对象
int f();
vector<int> vi(100);
int? r1 = f();//右值引用,返回非引用类型的函数
int? r2 = vi[0];//左值引用,下标
int? r3 = r1;//左值引用,变量是左值,即使这个变量是右值引用(右值引用不等于右值)。
int? r4 = vi[0] * f();//右值引用,返回右值的表达式(右值短暂临时)
见练习13.44
13.6.2 移动构造函数和移动赋值运算符
以String
为例
#ifndef MyString_H
#define MyString_H
#include <memory>
#include <algorithm>
#include <iterator>
#include <iostream>
class MyString{
public:
MyString():MyString(""){}
MyString(const char*);
MyString(const MyString&);
MyString& operator=(const MyString&);
MyString(MyString &&) noexcept;//移动构造函数
MyString& operator=(MyString &&)noexcept;//移动赋值函数
~MyString();
public:
size_t size()const{return ends-elements;}
const char* begin()const{return elements;}
const char* end()const{return ends;}
private:
std::allocator<char> alloc;
std::pair<char*,char*>alloc_n_copy(const char*,const char*);
void free();
void range_initializer(const char *first, const char *last);
private:
char* elements;
char* ends;
};
#endif
MyString.cpp
#include "MyString.hpp"
std::pair<char*,char*>
MyString::alloc_n_copy(const char*b,const char*e){
auto data = alloc.allocate(e-b);
return {data,std::uninitialized_copy(b, e,data)};
}
void MyString::free(){
if(elements){
std::for_each(elements,ends,[this](char &c){alloc.destroy(&c);});
alloc.deallocate(elements,ends-elements);
}
}
void MyString::range_initializer(const char *first, const char *last){
auto newstr = alloc_n_copy(first, last);
elements = newstr.first;
ends = newstr.second;
std::cout << "void MyString::range_initializer(const char *first, const char *last)" << std::endl;
}
MyString::MyString(const char* s){
char * sl = const_cast<char*>(s);
while(*sl)
++sl;
range_initializer(s,++sl);
}
MyString::~MyString(){free();}
MyString& MyString::operator=(const MyString &rhs){
auto data = alloc_n_copy(rhs.begin(),rhs.end());
free();
elements = data.first;
ends = data.second;
std::cout << "call MyString& MyString::operator=(const MyString &rhs)" << std::endl;
return *this;
}
MyString::MyString(const MyString& ms){
range_initializer(ms.begin(),ms.end());
std::cout << "MyString::MyString(const MyString& ms)" << std::endl;
}
MyString::MyString(MyString && ms) noexcept:elements(ms.elements),ends(ms.ends){
std::cout << "MyString::MyString(MyString && ms)" << std::endl;
ms.elements = ms.ends = nullptr;
}
MyString& MyString::operator=(MyString && ms)noexcept{
if(this != &ms){
free();
elements = ms.elements;
ends = ms.ends;
ms.elements = ms.ends = nullptr;
}
std::cout << "MyString& MyString::operator=(MyString && ms)" << std::endl;
return *this;
}
Test.cpp
#include <memory>
#include <vector>
#include <iostream>
#include "MyString.hpp"
void foo(MyString x)
{
std::cout << x.begin() << std::endl;
}
void bar(const MyString& x)
{
std::cout << x.begin() << std::endl;
}
MyString baz()
{
MyString ret("world");
return ret;
}
int main(){
//参考了 https://blog.csdn.net/qq_24739717/article/details/104473034#t48
char text[] = "world";
std::cout << "---------------定义-----------------" << std::endl;
MyString s0;
MyString s1("hello");
MyString s2(s0);
MyString s3 = s1;
MyString s4(text);
s2 = s1;
std::cout << "---------------调用-----------------" << std::endl;
foo(s1);
bar(s1);
foo("temporary");
bar("temporary");
MyString s5 = baz();
std::cout << "---------------添加-----------------" << std::endl;
std::vector<MyString> 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");//避免拷贝
std::cout << "---------------输出-----------------" << std::endl;
for (const auto &s : svec) {
std::cout << s.begin() << std::endl;
}
}
---------------定义-----------------
void MyString::range_initializer(const char *first, const char *last)
void MyString::range_initializer(const char *first, const char *last)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
call MyString& MyString::operator=(const MyString &rhs)
---------------调用-----------------
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
hello
hello
void MyString::range_initializer(const char *first, const char *last)
temporary
void MyString::range_initializer(const char *first, const char *last)
temporary
void MyString::range_initializer(const char *first, const char *last)
---------------添加-----------------
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(const MyString& ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(MyString && ms)
void MyString::range_initializer(const char *first, const char *last)
MyString::MyString(MyString && ms)
---------------输出-----------------
hello
hello
hello
world
world
world
good job
我们可以拷贝或赋值一个将要被销毁的unique_ptr
最常见的例子是从函数返回一个unique_ptr
实际上是调用了移动构造函数或移动赋值运算符接管此函数中unique_ptr
的所有权
左值拷贝,右值移动
hp = hp2;
hp2
是个左值,所以参数传递时通过拷贝构造函数来拷贝rhs
为hp2
的副本,两者独立,string
内容相同。赋值结束后rhs
被销毁
hp = std::move(hp2);
std::move
将一个右值引用绑定到hp2
上,使用移动构造函数rhs
接管hp2
所有权
不管使用的是拷贝构造还是移动构造,赋值运算符的函数体都会swap
两个运算对象的状态,交换两个对象指针后,rhs
中的指针指向原来左侧运算对象所用的string
。当rhs
离开作用域时,这个string
将被销毁
编译相关参数
clang++ -Wall -std=c++14 -O2 -fsanitize=undefined,address
#include <string>
#include <iostream>
#include <ctime>
class HasPtr {
public:
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=(HasPtr rhs){//拷贝赋值 swap版本
swap(*this,rhs);
return *this;
}
HasPtr(HasPtr && hp)noexcept:ps(hp.ps),i(hp.i){//移动构造
hp.ps = nullptr;
}
~HasPtr() {delete ps;}
private:
std::string *ps;
int i;
};
inline
void swap(HasPtr& lhs, HasPtr& rhs){
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i,rhs.i);
}
int main(){
HasPtr hp1("test1"),hp2("test2");
clock_t start = clock();
hp1 = hp2;
clock_t end = clock();
std::cout << end - start << std::endl;
return 0;
}
#include <string>
#include <iostream>
#include <ctime>
class HasPtr {
public:
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(HasPtr && hp)noexcept:ps(hp.ps),i(hp.i){//移动构造
hp.ps = nullptr;
}
HasPtr& operator=(const HasPtr& rhs){//拷贝赋值
auto newp = new std::string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
HasPtr& operator=(HasPtr&& rhs)noexcept{
if(this != &rhs){
delete ps;
ps = rhs.ps;
rhs.ps = nullptr;
}
return *this;
}
~HasPtr() {delete ps;}
private:
std::string *ps;
int i;
};
inline
void swap(HasPtr& lhs, HasPtr& rhs){
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i,rhs.i);
}
int main(){
HasPtr hp1("test1"),hp2("test2");
clock_t start = clock();
hp1 = hp2;
clock_t end = clock();
std::cout << end - start << std::endl;
return 0;
}
好像差距不大,甚至swap的更快?可能测的/写的有问题
参考其他前辈的题解说是swap
操作交换时都会创建临时空间用来存临时变量的值,所以赋值运算效率低
#include <string>
#include <iostream>
#include <ctime>
class HasPtr {
public:
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(HasPtr && hp)noexcept:ps(hp.ps),i(hp.i){//移动构造
hp.ps = nullptr;
}
HasPtr& operator=(HasPtr rhs){//拷贝赋值 swap版本
swap(*this,rhs);
return *this;
}
HasPtr& operator=(HasPtr&& rhs)noexcept{
if(this != &rhs){
delete ps;
ps = rhs.ps;
rhs.ps = nullptr;
}
return *this;
}
~HasPtr() {delete ps;}
private:
std::string *ps;
int i;
};
inline
void swap(HasPtr& lhs, HasPtr& rhs){
using std::swap;
swap(lhs.ps, rhs.ps);
swap(lhs.i,rhs.i);
}
int main(){
HasPtr hp1("test"),*hp2 = new HasPtr("train");
hp1 = std::move(*hp2);
return 0;
}
报错
error: use of overloaded operator '=' is ambiguous (with operand types 'HasPtr' and 'typename std::remove_reference<HasPtr &>::type' (aka 'HasPtr'))
13.6.3 右值引用和成员函数
void push_back(string &&t){data->push_back(std::move(t));}
Foo Foo::sorted() const & {
Foo ret(*this);
return ret.sorted();
}
ret
由拷贝构造函数产生,是一个左值,循环调用
Foo Foo::sorted() const & { return Foo(*this).sorted(); }
Foo(*this)
是个临时量,是右值,会调用右值版本的sorted()
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>
class Foo{
public:
Foo sorted() &&;
Foo sorted() const &;
private:
std::vector<int> data;
};
Foo Foo::sorted()&&{
sort(data.begin(),data.end());
std::cout << "Foo Foo::sorted()&&" << std::endl;
return *this;
}
Foo Foo::sorted() const &{
Foo ret(*this);
sort(ret.data.begin(),ret.data.end());
std::cout << "Foo Foo:sorted() const &" << std::endl;
return ret;
}
int main(){
Foo().sorted();//调用右值版本,如果没有的写右值版本会调用左值版本的
Foo f;
f.sorted();//调用左值版本
return 0;
}
输出
Foo Foo::sorted()&&
Foo Foo:sorted() const &