Chapter 13.5 dynamic memory management exercises and summary in C++ Primer

Keywords: less Lambda

13.5 dynamic memory management class

The best way to manage dynamic memory in a class is to use containers or smart pointers, but maybe if we want to design a set of libraries ourselves, we need to keep the performance of this library at a better level. At this time, we may need to use some lower level functions to manage memory.

If there is not enough space for a container like vector to push, the thief will open up a new space. At this time, you need to copy all the original elements into the new space.

Therefore, the copy constructor will be called here. Calling the copy constructor means that the performance needs to be consumed. Is there any method that can not call the copy constructor.

We can use the move constructor, or the key of std::move(), but std::move (cls) will also call the move constructor of the type passed in.

The details of the specific move are in the next section.

Practice

13.39

I divided it into header file and cpp file.
For resize() and reserve(), I refer to vector's guidelines.

For resize(n), reallocate memory if n is greater than capacity. If greater than size(), the default constructor is used for subsequent added objects
If less than size, destroy the last size()-n elements.

For reserve(n), if it is smaller than capacity(), then do nothing, if it is larger than capacity(), then reallocate the space.

Header file

#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*);
	//Free memory
	void free();
	void reallocate();
	//Point to first element
	std::string *elements;
	//Points to the next position of the last element
	std::string* first_free;
	//Point to the last position in space
	std::string *cap;
};

cpp file

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


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

StrVec::StrVec(const StrVec & vec)
{
	//Save as many elements as you want, so first "free will be equal to cap
	auto data = alloc_n_copy(vec.begin(),vec.end());
	elements = data.first;
	first_free = cap = data.second;
}

StrVec & StrVec::operator=(const StrVec &vec)
{	//You don't need to think about capacity here, because vec.end() is a pointer to 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: insert the return statement here
	return *this;
}

StrVec::~StrVec()
{
	free();
}
void StrVec::push_back(const std::string& s) {
	//Check the capacity before push ing
	chk_n_alloc();
	//Call structure
	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 will call the move constructor of 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

The question here should be why we use the post increment operation, and what if we use the pre increment operation.

Because the push back function uses a post increment operator in its construct.

If the pre increment operator is used, the first push back() operation may result in the location of the first UU free not being constructed. Or it can be constructed on an address where no memory is allocated, resulting in a program crash or undefined behavior.

13.42

Try not to change the previous code when replacing the container, so here I use a type alias to replace vector < string > so I just need to replace here with StrVec.

using str_container = StrVec;

Header file

#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 {
	//Lazy to write again, so use type alias
	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:
	//Do not use intra class initialization, use the parameters passed in by TextQuery to initialize
	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:
	//There are 50 lines by default
	TextQuery(ifstream& ifile) {
		string word;
		while (std::getline(ifile, word)) {
			text_content->push_back(word);
		}
	};
	QueryResult query(const string&);
private:
	//Because of the need to share data, these data members are all written in the form of smart pointers
	//I'm too lazy to initialize parameters in the initialization list. I'll use intra class initialization directly
	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 file

#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);
				//Add one to the number of Statistics
				++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 << "Enter the words you want to query" << endl;
		string s;
		if ((!(cin >> s) || s == "q")) {
			break;
		}
		print(cout, tq.query(s)) << endl;
	}
}

//Test code
ifstream f("data.txt");
runQueries(f);
13.43

I think it's better to use for each plus lambda, because we can write less repetitive code, such as for loop.
And it's more readable.

void StrVec::free()
{
	if (elements)
	{
		//The class type that the pointer points to is passed in the lambda expression
		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

Here I only implement the default constructor and the constructor that receives the C-style string pointer parameter, and think the end points to '\ 0'
Header file

#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 file

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

Test code

//MyString str;
	MyString str("213");
	print(cout, str);
Published 51 original articles, won praise 6, visited 3196
Private letter follow

Posted by maxudaskin on Tue, 25 Feb 2020 22:52:03 -0800