应该如何在循环中创建稍后需要使用的地图?
How should maps be created in a loop that need to be used later?
假设我有一个任意的 classes 层次结构。
Books
class 包含由 Chapter
class 的 object 组成的地图。每个 Chapter
都有自己的 object 的 Section
class.
地图
class 键是每个对应 object 的标题,例如如果我们有一本标题为 Moby Dick
的书,该书的 chapters
地图将包含 Loomings
和 The Spouter-Inn
等章节。
这里可能有一组简单的 classes 来描述这种关系:
class Section {
public:
string title;
Section (string t) { title = t; };
};
class Chapter {
public:
map <string, Section *> sections;
string title;
Chapter (map <string, Section *> s, string t) { sections = s; title = t; };
};
class Book {
public:
map <string, Chapter *> chapters;
string title;
Book (map <string, Chapter *> c, string t) { chapters = c; title = t; };
};
如果我有 space-separated 本书、章节和小节标题的文本文件(我们就称之为 books.txt
),例如
Moby_Dick Loomings Section_I
Moby_Dick Loomings Section_II
Moby_Dick The_Spouter-Inn Section_I
Moby_Dick The_Spouter-Inn Section_II
Moby_Dick Loomings Section_I
Moby_Dick The_Carpet-Bag Section_I
Moby_Dick The_Spouter-Inn Section_I
Moby_Dick The_Counterpane Section_I
Moby_Dick Breakfast Section_I
Moby_Dick The_Street Section_I
Frankenstein Chapter_1 Section_I
Frankenstein Chapter_1 Section_II
Frankenstein Chapter_2 Section_I
Frankenstein Chapter_2 Section_II
Frankenstein Chapter_2 Section_III
Frankenstein Chapter_3 Section_I
Frankenstein Chapter_4 Section_I
Frankenstein Chapter_5 Section_I
Frankenstein Chapter_6 Section_I
Pride_and_Prejudice Chapter_1 Section_I
Pride_and_Prejudice Chapter_2 Section_I
Pride_and_Prejudice Chapter_3 Section_I
Pride_and_Prejudice Chapter_4 Section_I
Pride_and_Prejudice Chapter_5 Section_I
Pride_and_Prejudice Chapter_6 Section_I
等等,我可能会用 ifstream
打开文件并处理每一行,将一本新书 object 添加到图书地图,然后添加到那本书 object的章节映射每个章节,每个章节的部分映射每个部分,等等。
假设最终目标是将描述这些书籍、章节和部分的文件行解析为地图,然后能够按字母顺序列出它们。
所以我从收集书籍开始:
#include <iostream> // cout, cerr, etc.
#include <cstdlib> // exit, EXIT_FAILURE
#include <string>
#include <fstream> // ifstream
#include <sstream> // istringstream
#include <fstream> // ifstream
#include <map>
using namespace std;
class Section {
public:
string title;
Section (string t) { title = t; };
};
class Chapter {
public:
map <string, Section *> sections;
string title;
Chapter (map <string, Section *> s, string t) { sections = s; title = t; };
};
class Book {
public:
map <string, Chapter *> chapters;
string title;
Book (map <string, Chapter *> c, string t) { chapters = c; title = t; };
};
int main() {
map <string, Book *> books;
string filename = "books.txt";
ifstream fin(filename.c_str()); // open file
string line;
if (fin.is_open()) {
while (getline(fin, line)) {
istringstream sin(line);
string bookTitle, chapterTitle, sectionTitle;
sin >> bookTitle >> chapterTitle >> sectionTitle;
if (books.count(bookTitle)) {
// the book is already in our map
// ... now what?
} else {
// the book doesn't exist yet, so go ahead and create fresh book, chapter, and section objects (and their maps)
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter *> chapters; // create new chapters map
chapters[chapterTitle] = & chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
books.insert(make_pair(bookTitle, & book)); // add book to books map
}
}
}
fin.close();
map <string, Book *>::iterator book_iter;
// print our resulting book map
for (book_iter = books.begin(); book_iter != books.end(); book_iter++) {
cout << "book: " << book_iter->first << endl;
}
}
很简单,效果很好:
$ g++ -Wall -Wextra -std=c++98 -o books books.cpp && ./books
book: Frankenstein
book: Moby_Dick
book: Pride_and_Prejudice
问题是当我需要向图书地图中的 现有 本书添加章节时。
所以首先我展开这个部分:
if (books.count(bookTitle)) {
// the book is already in our map
// ... now what?
} else {
变成:
if (books.count(bookTitle)) {
// the book is already in our map
Book book = (*books.at(bookTitle));
if (book.chapters.count(chapterTitle)) {
// the chapter is already in our map
// ... now what?
} else {
// the chapter doesn't exist yet, so go ahead and create a fresh chapter and section
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
book.chapters.insert(make_pair(chapterTitle, & chapter));
}
} else {
但是现在当我 运行 代码时,我得到:
Segmentation fault: 11
导致段错误的具体原因是 book.chapters.count(chapterTitle)
- 我假设是因为 book.chapters
不再可访问。
随着循环的每次迭代发生,在该迭代范围内创建的变量被清除,然后我尝试返回并再次引用该映射,它就消失了。
那我该怎么办?我不确定处理此问题的正确方法是什么。
您的代码存在一个主要问题。
如果您存储指向某个对象的指针,您自己有责任确保该对象在您计划使用该指针期间保持活动状态。 C++ 没有垃圾回收。具有自动存储持续时间的变量一旦超出范围就会消失。
} else {
// the book doesn't exist yet, so go ahead and create fresh book, chapter, and section objects (and their maps)
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter *> chapters; // create new chapters map
chapters[chapterTitle] = & chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
books.insert(make_pair(bookTitle, & book)); // add book to books map
}
一旦我们到达该代码块的末尾,您在堆栈上创建的书籍、章节和部分将被销毁。但是您已经在容器中插入了指向它们的指针。现在,一旦您尝试使用这些指针,它们就会悬空。它们将指向对象曾经存在于堆栈中的位置,但它们已经被销毁了。
如果您真的想在地图中存储指针,我建议您使用智能指针。他们很聪明,因为他们会为你管理对象的生命周期。
但在你的情况下,我看不出有任何理由不直接在地图中存储对象。
完成那部分内容后,我们可以继续讨论逻辑了。
Map::insert 的描述如下:
Inserts element(s) into the container, if the container doesn't already contain an element with an equivalent key.
Return value
1-3) Returns a pair consisting of an iterator to the inserted element (or to the element that prevented the insertion) and a bool denoting whether the insertion took place.
有了这些知识,我们可以将您的代码逻辑稍微简化为:
auto [book, book_inserted] = books.insert(make_pair(bookTitle, Book({}, bookTitle)));
现在 book 是指向以下之一的迭代器:
- 我们刚刚创建的新空书
- 已经在地图中
bookTitle
下的书
book_inserted
告诉我们是否真的插入了空书,或者是否已经有一本之前存储在该书名下的书。我们并不关心这两种方式,我们只需要迭代器。
接下来我们可以使用该迭代器插入章节
auto [chapter, chapter_inserted] = book->second.chapters.insert(make_pair(chapterTitle, Chapter({}, chapterTitle)));
最后,我们使用 chapter
迭代器插入该部分。
chapter->second.sections.insert(make_pair(sectionTitle, Section(sectionTitle)));
这是一个完整的例子
#include <iostream> // cout, cerr, etc.
#include <cstdlib> // exit, EXIT_FAILURE
#include <string>
#include <fstream> // ifstream
#include <sstream> // istringstream
#include <fstream> // ifstream
#include <map>
using namespace std;
class Section {
public:
string title;
Section (string t) { title = t; };
};
class Chapter {
public:
map <string, Section> sections;
string title;
Chapter (map <string, Section> s, string t) { sections = s; title = t; };
};
class Book {
public:
map <string, Chapter> chapters;
string title;
Book (map <string, Chapter> c, string t) { chapters = c; title = t; };
};
int main() {
map <string, Book> books;
string filename = "books.txt";
ifstream fin(filename.c_str()); // open file
string line;
if (fin.is_open()) {
while (getline(fin, line)) {
istringstream sin(line);
string bookTitle, chapterTitle, sectionTitle;
sin >> bookTitle >> chapterTitle >> sectionTitle;
auto [book, book_inserted] = books.insert(make_pair(bookTitle, Book({}, bookTitle)));
auto [chapter, chapter_inserted] = book->second.chapters.insert(make_pair(chapterTitle, Chapter({}, chapterTitle)));
chapter->second.sections.insert(make_pair(sectionTitle, Section(sectionTitle)));
}
}
fin.close();
// print our resulting book map
for (auto book_iter = books.begin(); book_iter != books.end(); book_iter++) {
cout << "book: " << book_iter->first << endl;
}
}
问题的核心是
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter *> chapters; // create new chapters map
chapters[chapterTitle] = & chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
全部通过当前代码块声明自动变量scoped并存储指向前面对象的指针。这一切都很好,因为它们都会超出范围并在相同范围内以安全顺序被销毁。没有容器比容器长寿。
但是...
books.insert(make_pair(bookTitle, & book));
存储指向 soon-to-expire 变量的指针 book
并且 books
将比 book
更有效。这是致命的。
简单的解决方案是没有指针。每个容器直接包含它们包含的对象而不是对它们的引用。现在包含的不能在容器之前超出范围。容器将免费管理所包含的销毁,减轻您肩上的内存管理负担。
class Section {
public:
string title;
Section (string t): title(t) { }; // using member initializer list rather than
// assignment in the body of the constructor
// allows the compiler to more easily apply
// many useful optimizations
};
class Chapter {
public:
map <string, Section> sections;
string title;
Chapter (map <string, Section> s, string t): sections(s), title(t) { };
class Book {
public:
map <string, Chapter> chapters;
string title;
Book (map <string, Chapter> c, string t): chapters(c), title(t) { };
};
现在
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter *> chapters; // create new chapters map
chapters[chapterTitle] = & chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
books.insert(make_pair(bookTitle, & book)); // add book to books map
变成
Section section (sectionTitle); // create new section object
map <string, Section> sections;
sections[sectionTitle] = section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter> chapters; // create new chapters map
chapters[chapterTitle] = chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
books.insert(make_pair(bookTitle, book)); // add book to books map
section
被复制到 sections
中,sections
被复制到 chapter
中,依此类推...一直向下复制。原件可能会超出范围并过期,而副本会一直存在,直到被移除或容器被销毁。
复制听起来很丑陋,但现代 C++ 编译器很聪明,提供了一些更快的替代方法,例如使用移动语义来减少不必要的复制,现代编译器将在可能的情况下应用 copy elision,并可能进行移动或复制不必要。移动语义和复制省略可能会变得复杂并且会使这个答案变得混乱,因此如果有必要,最好在以后的问题中讨论它们。
问题底部的修改有一个类似的问题,有一个小皱纹:
Book book = (*books.at(bookTitle));
在 books
中复制了 book
。 Book book
不是指针或对 book
的引用,*books.at(bookTitle)
中的 *
表示“获取映射到 bookTitle
中的指针处的对象books
。代码块的其余部分对副本进行操作,副本超出范围,什么也没做。books
中的原始代码没有更改。
Book & book = (*books.at(bookTitle));
会对 books
中的 Book
做一个 reference 并且你可以使用引用来修改引用的 Book
.
最后我需要使用 new
关键字来分配内存,这样当程序离开变量初始化的范围时它就不会被释放。正如 user4581301 指出的那样,我的代码的问题是我在没有 new
的情况下初始化变量,所以一旦离开范围,它们就会被销毁。
这是我的代码的更新版本,它按预期工作,使用 new
和 delete
关键字正确分配和释放内存:
#include <iostream>
#include <cstdlib>
#include <string>
#include <fstream>
#include <sstream>
#include <fstream>
#include <map>
using namespace std;
class Section {
public:
string title;
Section (string t) { title = t; };
};
class Chapter {
public:
map <string, Section *> * sections;
string title;
Chapter (map <string, Section *> * s, string t) { sections = s; title = t; };
};
class Book {
public:
map <string, Chapter *> * chapters;
string title;
Book (map <string, Chapter *> * c, string t) { chapters = c; title = t; };
};
int main() {
map <string, Book *> * books = new map <string, Book *>();
string filename = "books.txt";
ifstream fin(filename.c_str()); // open file
string line;
if (fin.is_open()) {
while (getline(fin, line)) {
istringstream sin(line);
string bookTitle, chapterTitle, sectionTitle;
sin >> bookTitle >> chapterTitle >> sectionTitle;
if ((*books).count(bookTitle)) {
// the book is already in our map
Book * book = (*books).at(bookTitle);
if ((*book).chapters->count(chapterTitle)) {
// the chapter is already in our map
Chapter * chapter = (*book).chapters->at(chapterTitle);
if ((*chapter).sections->count(sectionTitle)) {
// the section is already in our map
// do nothing - it's a dupe!
} else {
// the section doesn't exist yet
Section * section = new Section (sectionTitle); // create new section object
(*chapter).sections->insert(make_pair(sectionTitle, section));
}
} else {
// the chapter doesn't exist yet, so go ahead and create a fresh chapter and section
Section * section = new Section (sectionTitle); // create new section object
map <string, Section *> * sections = new map <string, Section *>();
(*sections)[sectionTitle] = section;
Chapter * chapter = new Chapter(sections, chapterTitle); // create new chapter object
(*book).chapters->insert(make_pair(chapterTitle, chapter));
}
} else {
// the book doesn't exist yet, so go ahead and create fresh book, chapter, and section objects (and their maps)
Section * section = new Section(sectionTitle); // create new section object
map <string, Section *> * sections = new map <string, Section *>();
(*sections)[sectionTitle] = section;
Chapter * chapter = new Chapter(sections, chapterTitle); // create new chapter object
map <string, Chapter *> * chapters = new map <string, Chapter *>(); // create new chapters map
(*chapters)[chapterTitle] = chapter; // add chapter to chapters map
Book * book = new Book(chapters, bookTitle); // create new book object
(*books).insert(make_pair(bookTitle, book)); // add book to books map
}
}
}
fin.close();
map <string, Book *>::iterator book_iter;
// delete all existing objects we created with new
for (book_iter = (*books).begin(); book_iter != (*books).end(); book_iter++) {
Book * book = book_iter->second;
map <string, Chapter *> * chapters = (*book).chapters;
map <string, Chapter *>::iterator chapter_iter;
for (chapter_iter = (*chapters).begin(); chapter_iter != (*chapters).end(); chapter_iter++) {
Chapter * chapter = chapter_iter->second;
map <string, Section *> * sections = (*chapter).sections;
map <string, Section *>::iterator section_iter;
for (section_iter = (*sections).begin(); section_iter != (*sections).end(); section_iter++) {
Section * section = section_iter->second;
cout << "deleting section: " << (*section).title << endl;
delete section;
}
cout << "deleting chapter: " << (*chapter).title << endl;
delete sections;
delete chapter;
}
cout << "deleting book: " << (*book).title << endl;
delete chapters;
delete book;
}
delete books;
}
请注意,解除分配的内存非常重要,以免造成内存泄漏。使用 valgrind
之类的工具进行测试帮助,例如在这种情况下 运行 我的 books
二进制我得到:
==1742004== HEAP SUMMARY:
==1742004== in use at exit: 0 bytes in 0 blocks
==1742004== total heap usage: 207 allocs, 207 frees, 92,144 bytes allocated
==1742004==
==1742004== All heap blocks were freed -- no leaks are possible
==1742004==
==1742004== For lists of detected and suppressed errors, rerun with: -s
==1742004== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
感谢 super 和 user4581301 的精彩解答,感谢 Topological Sort 的宝贵意见!
假设我有一个任意的 classes 层次结构。
Books
class 包含由 Chapter
class 的 object 组成的地图。每个 Chapter
都有自己的 object 的 Section
class.
class 键是每个对应 object 的标题,例如如果我们有一本标题为 Moby Dick
的书,该书的 chapters
地图将包含 Loomings
和 The Spouter-Inn
等章节。
这里可能有一组简单的 classes 来描述这种关系:
class Section {
public:
string title;
Section (string t) { title = t; };
};
class Chapter {
public:
map <string, Section *> sections;
string title;
Chapter (map <string, Section *> s, string t) { sections = s; title = t; };
};
class Book {
public:
map <string, Chapter *> chapters;
string title;
Book (map <string, Chapter *> c, string t) { chapters = c; title = t; };
};
如果我有 space-separated 本书、章节和小节标题的文本文件(我们就称之为 books.txt
),例如
Moby_Dick Loomings Section_I
Moby_Dick Loomings Section_II
Moby_Dick The_Spouter-Inn Section_I
Moby_Dick The_Spouter-Inn Section_II
Moby_Dick Loomings Section_I
Moby_Dick The_Carpet-Bag Section_I
Moby_Dick The_Spouter-Inn Section_I
Moby_Dick The_Counterpane Section_I
Moby_Dick Breakfast Section_I
Moby_Dick The_Street Section_I
Frankenstein Chapter_1 Section_I
Frankenstein Chapter_1 Section_II
Frankenstein Chapter_2 Section_I
Frankenstein Chapter_2 Section_II
Frankenstein Chapter_2 Section_III
Frankenstein Chapter_3 Section_I
Frankenstein Chapter_4 Section_I
Frankenstein Chapter_5 Section_I
Frankenstein Chapter_6 Section_I
Pride_and_Prejudice Chapter_1 Section_I
Pride_and_Prejudice Chapter_2 Section_I
Pride_and_Prejudice Chapter_3 Section_I
Pride_and_Prejudice Chapter_4 Section_I
Pride_and_Prejudice Chapter_5 Section_I
Pride_and_Prejudice Chapter_6 Section_I
等等,我可能会用 ifstream
打开文件并处理每一行,将一本新书 object 添加到图书地图,然后添加到那本书 object的章节映射每个章节,每个章节的部分映射每个部分,等等。
假设最终目标是将描述这些书籍、章节和部分的文件行解析为地图,然后能够按字母顺序列出它们。
所以我从收集书籍开始:
#include <iostream> // cout, cerr, etc.
#include <cstdlib> // exit, EXIT_FAILURE
#include <string>
#include <fstream> // ifstream
#include <sstream> // istringstream
#include <fstream> // ifstream
#include <map>
using namespace std;
class Section {
public:
string title;
Section (string t) { title = t; };
};
class Chapter {
public:
map <string, Section *> sections;
string title;
Chapter (map <string, Section *> s, string t) { sections = s; title = t; };
};
class Book {
public:
map <string, Chapter *> chapters;
string title;
Book (map <string, Chapter *> c, string t) { chapters = c; title = t; };
};
int main() {
map <string, Book *> books;
string filename = "books.txt";
ifstream fin(filename.c_str()); // open file
string line;
if (fin.is_open()) {
while (getline(fin, line)) {
istringstream sin(line);
string bookTitle, chapterTitle, sectionTitle;
sin >> bookTitle >> chapterTitle >> sectionTitle;
if (books.count(bookTitle)) {
// the book is already in our map
// ... now what?
} else {
// the book doesn't exist yet, so go ahead and create fresh book, chapter, and section objects (and their maps)
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter *> chapters; // create new chapters map
chapters[chapterTitle] = & chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
books.insert(make_pair(bookTitle, & book)); // add book to books map
}
}
}
fin.close();
map <string, Book *>::iterator book_iter;
// print our resulting book map
for (book_iter = books.begin(); book_iter != books.end(); book_iter++) {
cout << "book: " << book_iter->first << endl;
}
}
很简单,效果很好:
$ g++ -Wall -Wextra -std=c++98 -o books books.cpp && ./books
book: Frankenstein
book: Moby_Dick
book: Pride_and_Prejudice
问题是当我需要向图书地图中的 现有 本书添加章节时。
所以首先我展开这个部分:
if (books.count(bookTitle)) {
// the book is already in our map
// ... now what?
} else {
变成:
if (books.count(bookTitle)) {
// the book is already in our map
Book book = (*books.at(bookTitle));
if (book.chapters.count(chapterTitle)) {
// the chapter is already in our map
// ... now what?
} else {
// the chapter doesn't exist yet, so go ahead and create a fresh chapter and section
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
book.chapters.insert(make_pair(chapterTitle, & chapter));
}
} else {
但是现在当我 运行 代码时,我得到:
Segmentation fault: 11
导致段错误的具体原因是 book.chapters.count(chapterTitle)
- 我假设是因为 book.chapters
不再可访问。
随着循环的每次迭代发生,在该迭代范围内创建的变量被清除,然后我尝试返回并再次引用该映射,它就消失了。
那我该怎么办?我不确定处理此问题的正确方法是什么。
您的代码存在一个主要问题。
如果您存储指向某个对象的指针,您自己有责任确保该对象在您计划使用该指针期间保持活动状态。 C++ 没有垃圾回收。具有自动存储持续时间的变量一旦超出范围就会消失。
} else {
// the book doesn't exist yet, so go ahead and create fresh book, chapter, and section objects (and their maps)
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter *> chapters; // create new chapters map
chapters[chapterTitle] = & chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
books.insert(make_pair(bookTitle, & book)); // add book to books map
}
一旦我们到达该代码块的末尾,您在堆栈上创建的书籍、章节和部分将被销毁。但是您已经在容器中插入了指向它们的指针。现在,一旦您尝试使用这些指针,它们就会悬空。它们将指向对象曾经存在于堆栈中的位置,但它们已经被销毁了。
如果您真的想在地图中存储指针,我建议您使用智能指针。他们很聪明,因为他们会为你管理对象的生命周期。
但在你的情况下,我看不出有任何理由不直接在地图中存储对象。
完成那部分内容后,我们可以继续讨论逻辑了。
Map::insert 的描述如下:
Inserts element(s) into the container, if the container doesn't already contain an element with an equivalent key.
Return value 1-3) Returns a pair consisting of an iterator to the inserted element (or to the element that prevented the insertion) and a bool denoting whether the insertion took place.
有了这些知识,我们可以将您的代码逻辑稍微简化为:
auto [book, book_inserted] = books.insert(make_pair(bookTitle, Book({}, bookTitle)));
现在 book 是指向以下之一的迭代器:
- 我们刚刚创建的新空书
- 已经在地图中
bookTitle
下的书
book_inserted
告诉我们是否真的插入了空书,或者是否已经有一本之前存储在该书名下的书。我们并不关心这两种方式,我们只需要迭代器。
接下来我们可以使用该迭代器插入章节
auto [chapter, chapter_inserted] = book->second.chapters.insert(make_pair(chapterTitle, Chapter({}, chapterTitle)));
最后,我们使用 chapter
迭代器插入该部分。
chapter->second.sections.insert(make_pair(sectionTitle, Section(sectionTitle)));
这是一个完整的例子
#include <iostream> // cout, cerr, etc.
#include <cstdlib> // exit, EXIT_FAILURE
#include <string>
#include <fstream> // ifstream
#include <sstream> // istringstream
#include <fstream> // ifstream
#include <map>
using namespace std;
class Section {
public:
string title;
Section (string t) { title = t; };
};
class Chapter {
public:
map <string, Section> sections;
string title;
Chapter (map <string, Section> s, string t) { sections = s; title = t; };
};
class Book {
public:
map <string, Chapter> chapters;
string title;
Book (map <string, Chapter> c, string t) { chapters = c; title = t; };
};
int main() {
map <string, Book> books;
string filename = "books.txt";
ifstream fin(filename.c_str()); // open file
string line;
if (fin.is_open()) {
while (getline(fin, line)) {
istringstream sin(line);
string bookTitle, chapterTitle, sectionTitle;
sin >> bookTitle >> chapterTitle >> sectionTitle;
auto [book, book_inserted] = books.insert(make_pair(bookTitle, Book({}, bookTitle)));
auto [chapter, chapter_inserted] = book->second.chapters.insert(make_pair(chapterTitle, Chapter({}, chapterTitle)));
chapter->second.sections.insert(make_pair(sectionTitle, Section(sectionTitle)));
}
}
fin.close();
// print our resulting book map
for (auto book_iter = books.begin(); book_iter != books.end(); book_iter++) {
cout << "book: " << book_iter->first << endl;
}
}
问题的核心是
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter *> chapters; // create new chapters map
chapters[chapterTitle] = & chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
全部通过当前代码块声明自动变量scoped并存储指向前面对象的指针。这一切都很好,因为它们都会超出范围并在相同范围内以安全顺序被销毁。没有容器比容器长寿。
但是...
books.insert(make_pair(bookTitle, & book));
存储指向 soon-to-expire 变量的指针 book
并且 books
将比 book
更有效。这是致命的。
简单的解决方案是没有指针。每个容器直接包含它们包含的对象而不是对它们的引用。现在包含的不能在容器之前超出范围。容器将免费管理所包含的销毁,减轻您肩上的内存管理负担。
class Section {
public:
string title;
Section (string t): title(t) { }; // using member initializer list rather than
// assignment in the body of the constructor
// allows the compiler to more easily apply
// many useful optimizations
};
class Chapter {
public:
map <string, Section> sections;
string title;
Chapter (map <string, Section> s, string t): sections(s), title(t) { };
class Book {
public:
map <string, Chapter> chapters;
string title;
Book (map <string, Chapter> c, string t): chapters(c), title(t) { };
};
现在
Section section (sectionTitle); // create new section object
map <string, Section *> sections;
sections[sectionTitle] = & section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter *> chapters; // create new chapters map
chapters[chapterTitle] = & chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
books.insert(make_pair(bookTitle, & book)); // add book to books map
变成
Section section (sectionTitle); // create new section object
map <string, Section> sections;
sections[sectionTitle] = section;
Chapter chapter (sections, chapterTitle); // create new chapter object
map <string, Chapter> chapters; // create new chapters map
chapters[chapterTitle] = chapter; // add chapter to chapters map
Book book (chapters, bookTitle); // create new book object
books.insert(make_pair(bookTitle, book)); // add book to books map
section
被复制到 sections
中,sections
被复制到 chapter
中,依此类推...一直向下复制。原件可能会超出范围并过期,而副本会一直存在,直到被移除或容器被销毁。
复制听起来很丑陋,但现代 C++ 编译器很聪明,提供了一些更快的替代方法,例如使用移动语义来减少不必要的复制,现代编译器将在可能的情况下应用 copy elision,并可能进行移动或复制不必要。移动语义和复制省略可能会变得复杂并且会使这个答案变得混乱,因此如果有必要,最好在以后的问题中讨论它们。
问题底部的修改有一个类似的问题,有一个小皱纹:
Book book = (*books.at(bookTitle));
在 books
中复制了 book
。 Book book
不是指针或对 book
的引用,*books.at(bookTitle)
中的 *
表示“获取映射到 bookTitle
中的指针处的对象books
。代码块的其余部分对副本进行操作,副本超出范围,什么也没做。books
中的原始代码没有更改。
Book & book = (*books.at(bookTitle));
会对 books
中的 Book
做一个 reference 并且你可以使用引用来修改引用的 Book
.
最后我需要使用 new
关键字来分配内存,这样当程序离开变量初始化的范围时它就不会被释放。正如 user4581301 指出的那样,我的代码的问题是我在没有 new
的情况下初始化变量,所以一旦离开范围,它们就会被销毁。
这是我的代码的更新版本,它按预期工作,使用 new
和 delete
关键字正确分配和释放内存:
#include <iostream>
#include <cstdlib>
#include <string>
#include <fstream>
#include <sstream>
#include <fstream>
#include <map>
using namespace std;
class Section {
public:
string title;
Section (string t) { title = t; };
};
class Chapter {
public:
map <string, Section *> * sections;
string title;
Chapter (map <string, Section *> * s, string t) { sections = s; title = t; };
};
class Book {
public:
map <string, Chapter *> * chapters;
string title;
Book (map <string, Chapter *> * c, string t) { chapters = c; title = t; };
};
int main() {
map <string, Book *> * books = new map <string, Book *>();
string filename = "books.txt";
ifstream fin(filename.c_str()); // open file
string line;
if (fin.is_open()) {
while (getline(fin, line)) {
istringstream sin(line);
string bookTitle, chapterTitle, sectionTitle;
sin >> bookTitle >> chapterTitle >> sectionTitle;
if ((*books).count(bookTitle)) {
// the book is already in our map
Book * book = (*books).at(bookTitle);
if ((*book).chapters->count(chapterTitle)) {
// the chapter is already in our map
Chapter * chapter = (*book).chapters->at(chapterTitle);
if ((*chapter).sections->count(sectionTitle)) {
// the section is already in our map
// do nothing - it's a dupe!
} else {
// the section doesn't exist yet
Section * section = new Section (sectionTitle); // create new section object
(*chapter).sections->insert(make_pair(sectionTitle, section));
}
} else {
// the chapter doesn't exist yet, so go ahead and create a fresh chapter and section
Section * section = new Section (sectionTitle); // create new section object
map <string, Section *> * sections = new map <string, Section *>();
(*sections)[sectionTitle] = section;
Chapter * chapter = new Chapter(sections, chapterTitle); // create new chapter object
(*book).chapters->insert(make_pair(chapterTitle, chapter));
}
} else {
// the book doesn't exist yet, so go ahead and create fresh book, chapter, and section objects (and their maps)
Section * section = new Section(sectionTitle); // create new section object
map <string, Section *> * sections = new map <string, Section *>();
(*sections)[sectionTitle] = section;
Chapter * chapter = new Chapter(sections, chapterTitle); // create new chapter object
map <string, Chapter *> * chapters = new map <string, Chapter *>(); // create new chapters map
(*chapters)[chapterTitle] = chapter; // add chapter to chapters map
Book * book = new Book(chapters, bookTitle); // create new book object
(*books).insert(make_pair(bookTitle, book)); // add book to books map
}
}
}
fin.close();
map <string, Book *>::iterator book_iter;
// delete all existing objects we created with new
for (book_iter = (*books).begin(); book_iter != (*books).end(); book_iter++) {
Book * book = book_iter->second;
map <string, Chapter *> * chapters = (*book).chapters;
map <string, Chapter *>::iterator chapter_iter;
for (chapter_iter = (*chapters).begin(); chapter_iter != (*chapters).end(); chapter_iter++) {
Chapter * chapter = chapter_iter->second;
map <string, Section *> * sections = (*chapter).sections;
map <string, Section *>::iterator section_iter;
for (section_iter = (*sections).begin(); section_iter != (*sections).end(); section_iter++) {
Section * section = section_iter->second;
cout << "deleting section: " << (*section).title << endl;
delete section;
}
cout << "deleting chapter: " << (*chapter).title << endl;
delete sections;
delete chapter;
}
cout << "deleting book: " << (*book).title << endl;
delete chapters;
delete book;
}
delete books;
}
请注意,解除分配的内存非常重要,以免造成内存泄漏。使用 valgrind
之类的工具进行测试帮助,例如在这种情况下 运行 我的 books
二进制我得到:
==1742004== HEAP SUMMARY:
==1742004== in use at exit: 0 bytes in 0 blocks
==1742004== total heap usage: 207 allocs, 207 frees, 92,144 bytes allocated
==1742004==
==1742004== All heap blocks were freed -- no leaks are possible
==1742004==
==1742004== For lists of detected and suppressed errors, rerun with: -s
==1742004== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
感谢 super 和 user4581301 的精彩解答,感谢 Topological Sort 的宝贵意见!