0
点赞
收藏
分享

微信扫一扫

C++ Primer 0x0D 练习题解

老王420 2022-01-27 阅读 105

📔 C++ Primer 0x0D 练习题解

更好的阅读体验(实时更新与修正)

推荐阅读 《C++ Primer 5th》知识点总结&练习题解

13.1 拷贝、赋值与销毁

13.1.1 拷贝构造函数

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

当拷贝初始化的时候会使用拷贝构造函数

拷贝初始化不仅在我们用=定义变量时发生,在下列情况也会发生

  • 将一个对象作为实参传递给一个非引用类型的形参
  • 从一个返回类型为非引用类型的函数返回一个对象
  • 用花括号列表初始化一个数组中的元素或聚合类中的成员
  • 某些类型还会对它们所分配的对象使用拷贝初始化(insertpush会进行拷贝初始化,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它所指向的对象
    • 与普通指针不同,智能指针是类类型,所以具有析构函数。智能指针成员在析构阶段会被自动销毁

合成析构函数

  • 当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数
  • 对于某些类,合成析构函数被用来阻止该类型的对象被销毁
  • 一般,合成析构函数的函数体为空
  • **认识到析构函数体自身并不直接销毁成员很重要。成员是在析构函数体之后隐含的析构阶段被销毁的。**在整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分进行的

StrBlobshared_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次

item1item2accum离开作用域被销毁

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 拷贝控制实例

因为saveremove是要操作到某个具体的对象,而不是某一类

#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;
}

合成的拷贝控制成员可能导致赋值后已存在的FoldersMessage不同步

对于动态分配内存的例子来说,拷贝交换方式是一种简洁的设计。而这里的 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更简洁,而且可以避免越界

参考书上的StrVecapplenon

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是个左值,所以参数传递时通过拷贝构造函数来拷贝
  • rhshp2的副本,两者独立,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 &
举报

相关推荐

0 条评论