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
这里的问题应该是为什么使用后置递增运算,如果使用前置递增运算会怎么样。
因为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);