C++ Primer 第十三章 13.5 动态内存管理类 练习和总结

13.5 动态内存管理类

类中管理动态的内存最好的方法是使用容器或者使用智能指针,但是可能如果我们自己要设计一套库,那么就需要让这个库的性能处于一个较好的水平。这个时候我们可能需要使用一些较为低层的管理内存的函数。

像vector这样的容器如果push的时候空间不足,贼会开辟新的空间,此时需要将原来的元素都拷贝到新的空间中去。

所以这里会调用拷贝构造函数,调用拷贝构造函数意味着需要消耗性能,那么有没有什么方法可以不调用拷贝构造函数。

我们可以使用移动构造函数,或者使用std::move()的关键,但是std::move(cls)其实也将调用传入的类型的移动构造函数。

关于具体move的细节在下一节。

练习

13.39

我分为了头文件和cpp文件。
对于resize()和reserve()我参考vector的准则。

对于resize(n)如果n大于capacity则重新分配内存。如果大于size(),则为后续添加的对象使用默认构造函数
如果小于size,则销毁最后size()-n个元素的元素。

对于reserve(n)如果,如果小于capacity()则什么都不做,如果大于capacity()则重新分配空间。

头文件

#pragma once
#include<string>
#include<memory>
using std::string;
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);
	void resize(size_t);
private:
	static std::allocator<string> alloc;
	void chk_n_alloc() {
		if (first_free==cap) {
			reallocate();
		}
	};
	std::pair<string*, string*> alloc_n_copy(const string*,const string*);
	//释放内存
	void free();
	void reallocate();
	//指向第一个元素
	std::string *elements;
	//指向最后一个元素的下一位置
	std::string* first_free;
	//指向空间最后的位置
	std::string *cap;
};

cpp文件

#include "pch.h"
#include "StrVec.h"
#include<iostream>
using std::cout;
using std::endl;


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

StrVec::StrVec(const StrVec & vec)
{
	//保存一样多的元素,所以first_free将会等于cap
	auto data = alloc_n_copy(vec.begin(),vec.end());
	elements = data.first;
	first_free = cap = data.second;
}

StrVec & StrVec::operator=(const StrVec &vec)
{	//这里不用考虑容量,因为vec.end()是first_free的指针
	auto data = alloc_n_copy(vec.begin(), vec.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	//cap = elements + vec.capacity();
	// TODO: 在此处插入 return 语句
	return *this;
}

StrVec::~StrVec()
{
	free();
}
void StrVec::push_back(const std::string& s) {
	//push之前先检查容量够不够
	chk_n_alloc();
	//调用构造
	alloc.construct(first_free++,s);
}

void StrVec::reserve(size_t size)
{
	if (size>capacity())
	{
		auto temp_elements = alloc.allocate(size);
		auto temp_iter = temp_elements;
		for (auto iter = elements; iter != first_free; ++iter) {
			//cout << *iter << endl;
			alloc.construct(temp_iter++, *iter);
		}
		free();
		elements = temp_elements;
		first_free = temp_iter;
		cap = elements + size;
	}
}

void StrVec::resize(size_t size)
{
	if (size>capacity())
	{
		//free();
		auto temp_elements = alloc.allocate(size);
		auto temp_iter = temp_elements;
		//int i = 0;
		for (auto iter = elements; iter != first_free;++iter) {
			//++i;
			alloc.construct(temp_iter++, *iter);
		}
		for (;temp_iter!=temp_elements+size;++temp_iter)
		{
			alloc.construct(temp_iter);
		}
		free();
		elements = temp_elements;
		cap = first_free = temp_iter;
	}
	else if (size>this->size()) {
		for (; first_free != (elements + size);++first_free) {
			alloc.construct(first_free);
		}
	}
	else {
		for (size_t i=0;i<(this->size()-size);++i)
		{
			alloc.destroy(--first_free);
		}
	}
}

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

void StrVec::free()
{
	if (elements)
	{
		for (auto iter = first_free; iter != elements;) {
			alloc.destroy(--iter);
		}
		alloc.deallocate(elements, cap-elements);
	}
}

void StrVec::reallocate()
{
	auto newcapacity = size() ? 2 * size() : 1;
	auto newdata = alloc.allocate(newcapacity);

	auto dest = newdata;
	auto elem = elements;
	for (size_t i = 0;i!=size();++i){
		//move将调用string的移动构造函数
		alloc.construct(dest++,std::move(*elem++));
	}
	free();
	elements = newdata;
	first_free = dest;
	cap = elements + newcapacity;
}

13.40
StrVec::StrVec(const std::initializer_list<string>& init_list)
{
	auto data = alloc_n_copy(init_list.begin(), init_list.end());
	elements = data.first;
	first_free = cap = data.second;
}
13.41

这里的问题应该是为什么使用后置递增运算,如果使用前置递增运算会怎么样。

扫描二维码关注公众号,回复: 9460843 查看本文章

因为push_back函数的construct中使用的是后置递增运算符。

如果使用前置递增运算符,那么会导致第一次push_back()操作时可能导致first_free的位置没有被构造。或者在没有分配内存的地址上进行构造,从而造成程序崩溃或者产生未定义的行为。

13.42

在替换容器的时候尽量不要改变以前的代码,所以这里我使用了一个类型别名来代替vector<string>所以我只需要将这里替换为StrVec就可以了。

using str_container = StrVec;

头文件

#pragma once
#include<iostream>
#include<fstream>
//#include<sstream>
#include<string>
#include<memory>
#include<vector>
#include<map>
#include<set>
#include"StrVec.h"
using std::ostream;
using std::ifstream;
using std::set;
using std::map;
using std::vector;
using std::string;
using str_container = StrVec;
class QueryResult {
	//懒得重复写,所以使用类型别名
	using str_vec_ptr = std::shared_ptr<str_container>;
	using str_set_map_ptr = std::shared_ptr<map<string, set<size_t>>>;
	using str_map_ptr = std::shared_ptr<map<string, size_t>>;
	friend ostream& print(ostream& out, const QueryResult& result);
public:
	QueryResult(string word, str_vec_ptr p1, str_set_map_ptr p2, str_map_ptr p3) :query_word(word), text_content(p1), word_to_line_set_map(p2), word_count_map(p3) {

	}
	set<size_t>::iterator begin() {
		return (*word_to_line_set_map)[query_word].begin();
	};
	set<size_t>::iterator end() {
		return (*word_to_line_set_map)[query_word].end();
	};
	str_vec_ptr get_file() {
		return text_content;
	}
private:
	//不使用类内初始化,使用TextQuery传入的参数进行初始化
	str_vec_ptr text_content;
	str_set_map_ptr word_to_line_set_map;
	str_map_ptr word_count_map;
	string query_word;
};

class TextQuery {
public:
	//默认有50行
	TextQuery(ifstream& ifile) {
		string word;
		while (std::getline(ifile, word)) {
			text_content->push_back(word);
		}
	};
	QueryResult query(const string&);
private:
	//因为需要共享数据,所以这些数据成员全部写成智能指针的形式
	//懒得在初始化列表中初始化参数了,直接使用类内初始化
	std::shared_ptr<str_container> text_content = std::make_shared<str_container>();
	std::shared_ptr<map<string, set<size_t>>> word_to_line_set_map = std::make_shared<map<string, set<size_t>>>();
	std::shared_ptr<map<string, size_t>> word_count_map = std::make_shared<map<string, size_t>>();

};

cpp文件

#include "pch.h"
#include "TextQuery.h"
#include <sstream>

using std::cout;
using std::endl;
using std::cin;
QueryResult TextQuery::query(const string& str) {
	if ((*word_count_map).find(str) != (*word_count_map).end()) {
		return QueryResult(str, text_content, word_to_line_set_map, word_count_map);
	}
	size_t line = 1;
	size_t word_count = 0;
	set<size_t> word_appear_line_set;

	for (const auto& line_str : *text_content) {
		string single_word;
		std::istringstream single_text_stream(line_str);
		while (single_text_stream >> single_word) {
			if (str == single_word) {
				word_appear_line_set.insert(line);
				//统计次数的加一
				++word_count;

			}
		}
		++line;
	}
	(*word_to_line_set_map)[str] = word_appear_line_set;
	(*word_count_map)[str] = word_count;
	return QueryResult(str, text_content, word_to_line_set_map, word_count_map);
}

ostream& print(ostream& out, const QueryResult& result) {
	string target_word = result.query_word;
	out << target_word << " appear " << (*result.word_count_map)[target_word] << " times" << endl;
	for (const auto & line : (*result.word_to_line_set_map)[target_word]) {
		out << "line:" << line << " " << (*result.text_content)[line - 1] << endl;
	}
	return out;
}

void runQueries(ifstream &infile) {
	TextQuery tq(infile);
	while (true) {
		cout << "输入你要查询的词" << endl;
		string s;
		if ((!(cin >> s) || s == "q")) {
			break;
		}
		print(cout, tq.query(s)) << endl;
	}
}

测试代码
ifstream f("data.txt");
runQueries(f);
13.43

我觉得使用for_each加lambda的实现方式更好,因为我们可以少写一些重复的代码,比如for循环。
而且可读性更高

void StrVec::free()
{
	if (elements)
	{
		//lambda表达式中传入是指针指向的类类型
		std::for_each(elements, first_free, [](const string& item) {
			alloc.destroy(&item);
		});
		/*for (auto iter = first_free; iter != elements;) {
			alloc.destroy(--iter);
		}*/
		alloc.deallocate(elements, cap - elements);
	}
}

13.44

在这里我只实现了默认构造函数和接收C风格字符串指针参数的构造函数,而且认为end指向‘\0’
头文件

#pragma once
#include <memory>
class MyString{
	friend void print(std::ostream& s, const MyString& str);
public:
	MyString();
	MyString(const char*);
	~MyString();
	size_t get_char_arr_len(const char *);
private:
	static std::allocator<char> alloc;
	char* begin;
	char* end;
	char* last;
};

cpp文件

#include "pch.h"
#include "MyString.h"
#include <algorithm>
#include <iostream>
std::allocator<char> MyString::alloc;


MyString::MyString()
{
	begin = alloc.allocate(1);
	alloc.construct(begin,'\0');
	end = begin;
	last = end + 1;
}

MyString::MyString(const char * c)
{
	size_t len = get_char_arr_len(c)+1;
	begin = alloc.allocate(len);
	end = begin + len-1;
	last = end + 1;
	size_t index = 0;
	for (auto iter= begin;iter!=end;++iter)
	{
		alloc.construct(iter, c[index]);
		++index;
	}
	*end = '\0';
}


MyString::~MyString()
{
	std::for_each(begin, end+1, [](const char& item) {
		alloc.destroy(&item);
	});
	alloc.deallocate(begin,last-begin);
}

size_t MyString::get_char_arr_len(const char * c)
{
	size_t len = 0;
	while (*c!='\0')
	{
		++len;
		++c;
	}
	return len;
}

void print(std::ostream& s,const MyString& str) 
{
	std::for_each(str.begin, str.end, [&s](const char& item) {
		s << item;
	});
}

测试代码

//MyString str;
	MyString str("213");
	print(cout, str);
发布了54 篇原创文章 · 获赞 6 · 访问量 3297

猜你喜欢

转载自blog.csdn.net/zengqi12138/article/details/104514044