将大型对象放入堆中的最佳方式是什么
What is the best way to put large objects on the heap?
我正在处理一个项目,该项目需要从数据文件加载许多对象并将它们存储在内存中。由于有人告诉我堆栈空间很少,而且堆上应该有大量数据,所以我把所有数据都放在堆上。然而,我的印象是我做得有点过火了。
我目前的设计是这样的:
class RoadMap
{
unique_ptr<set<unique_ptr<Node>>> allNodes;
void addNode(unique_ptr<Node> node)
{
this->allNodes->insert(std::move(node));
}
}
int main()
{
unique_ptr<RoadMap> map(new RoadMap());
// open file etc.
for (auto nodeData : nodesInFile)
{
map->addNode(unique_ptr<Node>(new Node(nodeData)));
}
}
据我现在所知,这会产生很多开销,因为其中涉及到许多我认为不需要的独特指针。如果我理解正确,在"指针链"中只有一个唯一的指针屏障就足够了。然而,我不确定这样做的最佳做法是什么。
选项1
class RoadMap
{
unique_ptr<set<Node>> allNodes;
void addNode (Node node)
{
this->allNodes->insert(node);
}
}
int main()
{
RoadMap map;
//open file etc.
for (auto nodeData : nodesInFile)
{
map.addNode(Node(nodeData));
}
}
在我看来,这样做的好处是RoadMap
类本身是唯一需要处理堆分配的类,并且在创建set
时只需要处理一次。
选项2
class RoadMap
{
set<Node> allNodes;
void addNode (Node node)
{
this->allNodes.insert(node);
}
}
int main()
{
unique_ptr<RoadMap> map(new RoadMap());
// open file etc.
for (auto nodeData : nodesInFile)
{
map->addNode(Node(nodeData));
}
}
这里唯一的指针只在主函数中,这意味着RoadMap
类的用户需要知道这个对象可能会变得很大,应该放在堆栈中。我不认为这是一个太好的解决方案。
选项3
class RoadMap
{
set<unique_ptr<Node>> allNodes;
void addNode(unique_ptr<Node> node)
{
this->allNodes.insert(std::move(node));
{
}
int main()
{
RoadMap map;
// open file etc.
for (auto nodeData : nodesInFile)
{
map.addNode(unique_ptr<Node>(new Node(nodeData)));
}
}
该解决方案使用许多唯一指针,这意味着在删除RoadMap
时,需要调用许多析构函数和delete
s。此外,RoadMap
调用方在添加节点时必须提供unique_ptr
,这意味着他必须自己进行堆分配。
现在,我赞成方案1而不是其他方案。然而,我编写C++的时间相对较短,不确定我是否完全理解内存管理背后的概念,这就是为什么我希望你验证我的观点。我认为选项1是最好的方法,这是正确的吗?你有关于这类事情的最佳实践的其他参考资料吗?
给Node
一个移动构造函数和移动赋值运算符(使对集合的操作变得便宜),然后混合使用选项1和选项2。std::set
将已经在堆中分配其内容,因此您不必担心在堆上分配RoadMap
。注意addNode
内的额外std::move
,以允许将Node
移动到集合中。
class RoadMap
{
set<Node> allNodes;
void addNode (Node node)
{
allNodes.emplace(std::move(node));
}
};
int main()
{
RoadMap map;
// open file etc.
for (const auto& nodeData : nodesInFile)
{
map.addNode(Node(nodeData));
}
}
它们彼此都有很大的不同。
为了简单起见,我建议选择2。但在某些操作(如sort
等)中,它可能更需要性能,因为您将移动整个Node
,而不是指向它的指针。
我认为这不是问题,因为您使用的是set
。您仍然可以通过在Node
对象上使用移动语义来优化这一点。没有这个,你仍然使用一个副本,每次添加。
我提到的上述问题可能是vector
的问题。直接存储对象的另一个问题是缺乏多态性。你不能存储Node
的子类型,它们会被切片。
如果这是一个问题,我建议选择2。存储指针意味着移动指针更快,而多态性起作用。
我认为没有理由选择方案1或您的原始解决方案。
p.s.代码中的this->
是不必要的。
正如DyP所指出的,set
无论如何都使用堆,这就是选项2的优点。线索-基于堆栈的结构无法增长。=>我相信只有std::array
存储在堆栈中。
让我谈谈元问题:您不希望堆栈溢出,从而将数据结构放在堆上。这是正确的做法。但重要的是要理解事情什么时候会被搁置。
每个局部变量都在堆栈上分配。如果您有动态大小的数据结构,那么它们在(大多数)所有情况下都引用堆。(我知道的唯一例外是,当您故意使用alloca()
或std::get_temporary_buffer()
或类似的东西在堆栈上保留内存时)。特别是所有STL容器都将其内存保留在堆上,几乎不使用任何用于局部变量或成员变量的堆栈内存(除了std::array
,其大小在编译时已知)。
因此,如果您想节省堆栈内存,将动态大小的数据结构封装到unique_ptrs
中效果很小,但它会给程序增加间接性,这会使代码复杂化,降低执行速度,并不必要地增加堆内存使用量。
以下是一个示例:在使用32位编译的Visual Studio 2010上,std::set
将在堆栈上使用20字节的内存,而与模板类型参数和集合中包含的实际数字元素无关。集合元素的内存在堆上。
我相信,您现在可以自行决定是否将unique_ptrs
用于您想要的目的。
基本上,它还取决于您希望如何访问RoadMap实例中存储的Node实例。我假设您的Node实例将释放封装的note数据。
我会选择调整后的版本2。
- 在c代码之间共享数据的最佳方式
- 使用QQuickFramebufferObject时同步数据的最佳方式是什么
- 从嵌套在std::映射中的std::列表中删除元素的最佳方式
- 如果条件为TRUE(最佳方式?),则在do while循环中后置增量
- 在reactor中存储eventHandlers的最佳方式是什么
- 在AVX通道中混洗的最佳方式
- 从 T 创建 std::future 的最佳方式<T>
- C++:使用 std::unique_ptr 访问重载运算符++的最佳方式?
- 对列表列表中的元素进行分组的最佳方式
- 利用 GPU 的最佳方式
- 使用 QT C++过滤大数据的最佳方式
- 算法设计:用边界数字表示 2D 网格的最佳方式,以C++?
- 在C++中共享键值对的最佳方式
- 为Catch2中的外部文本文件指定路径的最佳方式
- 代表Quarto棋盘游戏棋子的最佳方式
- 等待线程的最佳方式是什么
- 将uint8_t*buffer和size_tbufferlen从C++传递到C中的API函数的最佳方式是什么
- 创建控制台菜单C++的最佳方式
- 只显示片段着色器的最佳方式是什么
- 复制文件的最佳方式是什么,以便我可以在复制过程中轻松取消复制?